Playbook — windows-dev Remote Desktop via Cloudflare Tunnel (VNC, not RDP)

Status: ACTIVE Scope: Secure remote-screen access to the dev workstation (windows-dev) for lap2, without opening any inbound port.


Why VNC instead of RDP

windows-dev runs Windows 10 Home, which does not support acting as an RDP host (RDP server) at all — this is a hard SKU limitation, not a config toggle. Native rdp:// ingress through a CF Tunnel is therefore a dead end on this machine regardless of firewall/registry settings (fDenyTSConnections can be flipped but the RDP host service itself is crippled on Home). VNC (TightVNC) has no such licensing restriction and was used instead.

If windows-dev is ever upgraded to Windows 10/11 Pro, native RDP could replace this setup by:

  1. Enabling Remote Desktop in Settings
  2. Changing the tunnel ingress service: from tcp://localhost:5900 back to rdp://localhost:3389
  3. Deleting the TightVNC service/install (optional cleanup)

What was provisioned (2026-10-14)

  • TightVNC Server (GlavSoft.TightVNC via winget), installed as Windows service tvnserver, Running/Automatic. Listens on 5900 — not exposed via Windows Firewall inbound rule (none added), so it’s only reachable via loopback (same-host), which is exactly what the tunnel needs. Primary password set interactively by the owner via the TightVNC tray icon → Configure → Security tab — never touched by Claude, never stored in SOPS (this is a local-only convenience credential, not an ecosystem secret).
  • Cloudflare Tunnel windows-dev-rdp (id 8e14f71a-6484-4d0e-890b-4cff5f19642b), locally managed (cert.pem + credentials JSON pattern, same as the existing local-dev tunnel on this machine — NOT the remotely-managed token pattern used for the fleet-backup SSH tunnels). Config: C:\Users\konar\.cloudflared\windows-dev-rdp-config.yml:
    tunnel: 8e14f71a-6484-4d0e-890b-4cff5f19642b
    credentials-file: C:/Users/konar/.cloudflared/8e14f71a-6484-4d0e-890b-4cff5f19642b.json
    ingress:
      - hostname: windows-dev-rdp.zintegrowana.online
        service: tcp://localhost:5900
      - service: http_status:404
  • Windows service cloudflared-windows-dev-rdp (created via sc.exe create, not cloudflared service install <token> — that command path is only for remotely-managed/token tunnels). binPath: cloudflared.exe tunnel --config <config above> run. start=auto, Running. Needs an elevated (-Verb RunAs) shell to create/start/restart — the interactive session’s own PowerShell is not elevated by default on this machine.
  • DNS CNAME windows-dev-rdp.zintegrowana.online → 8e14f71a-....cfargotunnel.com (via cloudflared tunnel route dns, proxied).
  • Cloudflare Access Application windows-dev-rdp (type: self_hosted — not type: rdp; the native rdp/ssh Access app types require Infrastructure Access target_criteria registration, a separate/heavier feature this account isn’t using. self_hosted with just domain is the same pattern the *-ssh apps already use (type: ssh + domain, no target_criteria) — Access enforcement is domain+policy based regardless of the cosmetic type field). App id ed10eeac-8dc2-43a0-94b6-8dd1f7888e2a.
  • Access Policy windows-dev-rdp-owner-only — decision: allow, include: [{email: radieu@gmail.com}]. Policy id 63248f6a-a877-4f2b-a5db-75466ad3e728. First connection per client machine triggers a one-time browser email-OTP login, cached ~24h after (same behavior as the *-ssh Access apps — see docs/playbooks/fleet-backup-ssh-cf-tunnel.md).

Required: allow loopback connections in TightVNC

By default TightVNC rejects connections that originate from 127.0.0.1. The tunnel’s cloudflared connector reaches the VNC server over loopback, so without this setting the client connects through the proxy but the session is dropped immediately (no desktop, no password prompt).

On windows-dev, enable either:

  • tray icon → Configuration → Server → tick Allow loopback connections; leave Don’t allow external connections ticked (keeps VNC unreachable from the LAN); or
  • elevated PowerShell:
    Set-ItemProperty 'HKLM:\SOFTWARE\TightVNC\Server' -Name AllowLoopback -Value 1 -Type DWord
    Restart-Service tvnserver

Connecting from lap2

# 1. Local TCP proxy through the Access-gated tunnel (first run = browser OTP login)
cloudflared access tcp --hostname windows-dev-rdp.zintegrowana.online --url 127.0.0.1:15900
 
# 2. Point any VNC client at the local proxy
#    (TigerVNC, RealVNC Viewer, or TightVNC Viewer — all interoperate)
#    Address: 127.0.0.1:15900
#    Password: the TightVNC primary password set on windows-dev (owner knows it, not in SOPS)

The cloudflared access tcp proxy is a plain foreground process on lap2 — it does not survive a restart or logout (autostart is not set up). The Access token expires after ~24h; renew with cloudflared access login --hostname windows-dev-rdp.zintegrowana.online and restart the proxy.

cloudflared must be installed on lap2 (winget install Cloudflare.cloudflared) for step 1.

Verifying health

$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
.\scripts\sops-invoke.ps1 -SopsFile secrets\administration.env.sops `
  -Keys '{"CF_GLOBAL_API_KEY":"CF_GLOBAL_API_KEY"}' `
  -Command 'Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/accounts/acc5b2c956dd488265003364e2c7f84a/cfd_tunnel?is_deleted=false" -Headers @{"X-Auth-Email"="radieu@gmail.com";"X-Auth-Key"=$env:CF_GLOBAL_API_KEY} | Select-Object -ExpandProperty result | Where-Object { $_.name -eq "windows-dev-rdp" } | Select-Object name,status'

Locally: Get-Service cloudflared-windows-dev-rdp (expect Running) and Get-Service tvnserver (expect Running).

Known gotchas

  • Service creation/restart needs elevation. sc.exe create/start and Restart-Service on this service fail with access-denied from a non-admin PowerShell — always relaunch via Start-Process powershell -Verb RunAs.
  • The shared cloudflared.exe Access-token lock (see docs/playbooks/fleet-backup-ssh-cf-tunnel.md §“one alias at a time only”) applies here too: don’t run cloudflared access ... for this hostname at the same time as another *-tunnel/*-ssh alias from the same client machine.
  • No firewall inbound rule for port 5900 was created on purpose — do not add one. The tunnel reaches the VNC server over loopback; an inbound LAN rule would needlessly expose VNC directly.
  • docs/playbooks/fleet-backup-ssh-cf-tunnel.md — the sibling SSH-over-tunnel pattern (remotely managed tunnels, Access apps, service-token gotchas)
  • docs/environments/windows-dev.md — the existing windows-dev-ssh tunnel (WSL2 guest, port 22, worker-dispatch SSH) — a different tunnel, unrelated to this one