Skip to content

bug(ansible/vnc): SecurityTypes=VeNCrypt makes VNC unreachable by every client; websockify can't read its TLS key; role provisions on unselected hosts #13060

Description

@mrveiss

Summary

Deploying the Ansible vnc role produces a VNC server no client can connect to, a websockify unit that cannot read its own TLS key, and installs the whole desktop stack on hosts that never selected the role. Three defects, all in autobot-slm-backend/ansible/.


1. SecurityTypes=VeNCrypt — no client can complete the handshake (blocking)

roles/vnc/defaults/main.yml:23 defaults vnc_security_type to VeNCrypt, rendered by roles/vnc/templates/vnc-config.j2:12 into ~/.vnc/config. TigerVNC then advertises only security type 19.

Observed on a real deploy:

Server offered VeNCrypt only (type 19)
noVNC (apt novnc) 1.0.0 — supports std_vnc_auth, tight_auth, xvp_auth
VeNCrypt support added in noVNC 1.4.0

Two independent reasons this is wrong, not just an old-client problem:

  • The role provisions VncAuth credentials. roles/vnc/tasks/main.yml:79 runs vncpasswd -f, which writes a VncAuth-format password file. The role sets up VncAuth auth and then configures a security type that never reads it.
  • VeNCrypt names only the wrapper, not a sub-type. TLSVnc / X509Vnc / Plain are the sub-types. With none enabled the sub-type list is empty, so even a VeNCrypt-capable client (noVNC >= 1.4.0, TigerVNC viewer) has nothing to negotiate. Upgrading noVNC alone does not fix this.

This is a canonical divergence — the Ansible role is the only launcher in the repo that does this. Every other one uses VncAuth:

  • autobot-backend/api/vnc_manager.py:143 — VncAuth,TLSVnc
  • autobot-infrastructure/shared/scripts/infrastructure/browser-vnc.service:18 — VncAuth,TLSVnc
  • autobot-infrastructure/shared/scripts/setup_browser_vnc.sh:57 — VncAuth,TLSVnc
  • autobot-infrastructure/shared/scripts/utilities/fix-vnc-desktop.sh:58 — VncAuth
  • docs/guides/vision-vnc-ui-testing.md:2316 — VncAuth,TLSVnc

This also explains intermittent behaviour: starting VNC through the backend API passes -SecurityTypes VncAuth,TLSVnc on the CLI and works, while roles/vnc/templates/vnc-server.service.j2:21 passes no -SecurityTypes, so ~/.vnc/config wins at boot and the server comes up unreachable.

VeNCrypt buys nothing on this hop regardless: roles/vnc/templates/vnc-websockify.service.j2:19-21 already terminates TLS for the browser (--cert/--key/--ssl-only), and roles/vnc/tasks/register-credentials.yml:70 registers the websockify port, not the raw RFB port. The raw hop is loopback websockify -> VNC.

Fix: default vnc_security_type to VncAuth,TLSVnc,X509Vnc.


2. websockify cannot read its own TLS key — --ssl-only unit fails to start

roles/vnc/templates/vnc-websockify.service.j2 runs as User={{ vnc_user }} (line 14) but reads /etc/autobot/certs/server-key.pem (line 20). That is the shared node key: roles/vnc/tasks/main.yml:125 chmods it 0600 with no chown, and playbooks/setup-internal-ca.yml:435 documents node keys as root:root 0600.

An unprivileged vnc_user cannot read a root-owned 0600 key, so the --ssl-only websockify unit dies on startup.

Fix: generate a dedicated VNC keypair owned by vnc_user, rather than loosening permissions on the shared node key that nginx, Redis, and PostgreSQL also consume. This also gives X509Vnc from #1 a cert/key the VNC server can actually read.


3. role_vnc_active provisions VNC on hosts that never selected it

Reported from a live deploy: the VNC stack landed on a host without vnc in its node_roles.

Duplicated in two places — playbooks/vars/role_active_facts.yml:71-75 and inventory/group_vars/all.yml:346-350:

role_vnc_active: >-
  {{ ('vnc' in (node_roles | default([]))) or
     (inventory_hostname in (groups.get('main', []) | default([]))) or      # <-- unwanted
     (inventory_hostname in (groups.get('backend', []) | default([]))) or   # <-- unwanted
     (inventory_hostname in (groups.get('vnc_nodes', []) | default([]))) }}

Any host in the main or backend group pulls in tigervnc, x11vnc, novnc, websockify and xfce4 with vnc absent from node_roles. VNC is the outlier: role_xrdp_active, defined immediately below, has no equivalent group fallback.

Fix (owner decision: explicit only): drop both the main and backend fallbacks in both copies, so VNC provisions only on 'vnc' in node_roles or membership in vnc_nodes — matching role_xrdp_active.

Migration note: backend/main hosts that relied on implicit activation must add vnc to their node_roles to keep the desktop provisioned. autobot-backend/api/vnc_manager.py:85 pgreps for Xtigervnc on the canonical display, so AdvancedControl/desktop on such a host needs the role explicitly selected from now on.


Verification plan

  • ansible-lint + yamllint on the changed role and vars files
  • Render vnc-config.j2 / vnc-websockify.service.j2 and assert SecurityTypes=VncAuth,TLSVnc,X509Vnc and a vnc_user-readable key path
  • Assert role_vnc_active is false for a host in backend without vnc in node_roles, and true when vnc is present (the tests/playbooks/test_role_active_facts*.yml harness already covers this shape)

Activity

  1. mrveiss commented on Jul 30, 2026

    @mrveiss
    OwnerAuthor

    Follow-up note: VncAuth truncates the password to 8 characters

    Recording this alongside the switch to VncAuth in #13061 so it is not lost.

    The role generates a ~16-character secret (openssl rand -base64 12 | tr -dc 'a-zA-Z0-9' | head -c 16, tasks/main.yml:60) and stores it in /etc/autobot/vnc-secrets.env. But vncpasswd -f writes an RFB VncAuth password, and that scheme silently keeps only the first 8 characters — a DES key-schedule limit in the protocol, not a bug in this role.

    Effective strength is therefore ~47 bits, not what the stored length suggests. Two consequences:

    Not expanded into #13061 to keep that PR scoped to the three reproduced defects. Worth a separate decision on whether to move the desktop path off VncAuth entirely (X509Vnc-only, or front it with the already-TLS websockify hop exclusively).

  2. mrveiss commented on Jul 30, 2026

    @mrveiss
    OwnerAuthor

    Fix opened as #13061 (branch issue-13060, base Dev_new_gui) — all three defects addressed in one PR.

  3. github-actions commented on Jul 30, 2026

    @github-actions
    Contributor

    PR #13061 (merged to Dev_new_gui) references this issue with a close keyword.

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

    If this issue is fully resolved, close it manually. If work remains, no action is needed.

  4. mrveiss commented on Jul 30, 2026

    @mrveiss
    OwnerAuthor

    Closed by #13061 — squash-merged to Dev_new_gui as df0c203a4.

    Closing manually because Closes #N only fires on the default branch.

    Verified on Dev_new_gui after merge

    $ git log --oneline -1 origin/Dev_new_gui
    df0c203a4 fix(ansible/vnc): offer VncAuth so clients can connect, give VNC its own
              TLS key, stop provisioning on unselected hosts (#13060) (#13061)
    
    # 1. security type
    roles/vnc/defaults/main.yml:36
    vnc_security_type: "{{ lookup('env','VNC_SECURITY_TYPE') | default('VncAuth,TLSVnc,X509Vnc', true) }}"
    
    # 2. dedicated VNC keypair
    roles/vnc/templates/vnc-websockify.service.j2:22-23
        --cert={{ vnc_tls_cert }} \
        --key={{ vnc_tls_key }} \
    
    # 3. explicit activation (both duplicated copies)
    role_vnc_active: >-
      {{ ('vnc' in (node_roles | default([]))) or
         (inventory_hostname in (groups.get('vnc_nodes', []) | default([]))) }}
    

    CI

    Head 57f825bc7, latest-run-per-check: 0 checks non-success and non-skipped. Merged via --squash --admin — the only blocker was REVIEW_REQUIRED from CODEOWNERS * @mrveiss, which makes self-review impossible.

    Tests

    Both role-active-facts playbooks passed across all 9 synthetic hosts (failed=0) on the group_vars and vars_files loading paths. A negative control forcing role_vnc_active=true failed the flipped assertion, proving it is live rather than silently skipped.

    Still open from this issue

  5. mrveiss commented on Jul 31, 2026

    @mrveiss
    OwnerAuthor

    Correction to finding #2 — the "websockify could never start" claim was overstated

    While investigating an unrelated incident I observed the live host directly, and it contradicts what I asserted here.

    I claimed websockify could never start because it runs as vnc_user while the shared node key is root:root 0600. I took the ownership from documentation — playbooks/setup-internal-ca.yml:435 describes node keys as root:root 0600 — rather than from the filesystem.

    Observed reality on a provisioned host:

    $ ls -l /etc/autobot/certs/server-key.pem
    -rw------- 1 autobot autobot 1704 /etc/autobot/certs/server-key.pem
    
    $ ps -eo user,cmd | grep websockify
    autobot  /usr/bin/python3 /usr/bin/websockify --web=/usr/share/novnc \
             --cert=/etc/autobot/certs/server-cert.pem \
             --key=/etc/autobot/certs/server-key.pem --ssl-only 0.0.0.0:6080 localhost:5901
    

    The key is owned by autobot, not root, and websockify is running fine with --ssl-only.

    What was accurate: the vnc role's own generation path (roles/vnc/tasks/main.yml) runs chmod 0600 under become: true with no chown, so when that path creates the file it lands root:root and websockify genuinely cannot read it. On this host something else — setup-internal-ca or the backend role — created it as autobot first, so the role's creates:-guarded generation never ran and the latent problem never surfaced.

    So the correct statement is: the failure is conditional on which path generates the shared key first, not the universal "could never start" I wrote. Any host where the vnc role wins that race gets a broken --ssl-only unit; hosts where another role wins are fine.

    No change to the fix. The dedicated vnc_user-owned keypair shipped in #13061 makes this deterministic regardless of ordering, and still avoids widening access to a key that nginx, Redis and PostgreSQL share. The remedy stands; only my justification for it was wrong.

  6. mrveiss commented on Jul 31, 2026

    @mrveiss
    OwnerAuthor

    Consolidation follow-up filed as #13148 — the divergence that caused this issue (Ansible role on VeNCrypt while all five other launchers were on VncAuth) is one of three hand-synchronised duplications, including the role_*_active facts this fix had to edit in two places.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions