noVNC: VNC klient běžící ve webovém prohlížeči

1. 3. 2013
Doba čtení: 7 minut

Sdílet

Když potřebujete provést úpravu na virtuálním serveru, můžete narazit na protokol VNC a často i VNC klienta implementovaného jako applet v Javě. Existuje ale také projekt, který dokáže to samé pouze s HTML5 a JavaScriptem. Jmenuje se noVNC, je uvolněn pod svobodnou licencí a není vůbec složité ho použít.

Webový prohlížeč už několik let nezobrazuje pouze statický obsah a pomalu se přestává chovat jen jako nástroj pro prezentaci informací. Důvodem je, že některé aplikace pomalu mizí z nativního světa a dostávají se do prostředí webu, které k tomu rozhodně nebylo navrženo.

Kdyby bylo možné zjistit, kolik lidí čte e-maily na webu a kolik lidí k tomu používá nativní aplikaci, určitě bychom se dostali k zajímavému poměru a dokáži si představit, že by to nativní aplikace prohrály na plné čáře. Kdo si například zvykne na rozhraní GMailu, jen těžko hledá alternativu u nativních aplikací. Jeden z nepopulárnějších nativních klientů, Thunderbird, už ani není vyvíjen organizací Mozilla, která už jen opravuje chyby a spravuje patche, které jí lidé posílají. Více informací k tomuto tématu najdete v našem článku Mozilla předává Thunderbird komunitě. Jaký bude?

Ať se nám to líbí nebo ne, vývoj jde směrem k webu a pokud se nějaká aktivně používaná nativní aplikace nepřizpůsobí trendu, mohli by se její vývojáři jednoho dne probudit a zjistit, že podstatná část uživatelů přešla na web. Důvody, proč lidé raději používají webové aplikace, nejsou předmětem tohoto článku, ale když budete číst dál, jeden se dozvíte.

Minulý týden jsem implementoval podporu pro libvirt v projektu Python Control Panel. To je hostingová administrace vyvíjená pro hosting Roští.cz a uvolněná pod BSD licencí. Potřeboval jsem přidat podporu pro správu virtuálních strojů a jeden z cílů, který jsem si vytyčil, byl co nejpohodlnější přístup ke konzoli stroje. Jaké k tomu jsou možnosti? Stejně jako řada dalších jsem se mohl uchýlit k VNC klientovi implementovaném jako applet napsaný v Javě. Další možností je použít virtuální sériovou konzoli serveru a nějakým způsobem s ní komunikovat pomocí webového prohlížeče. K tomu lze využít Anyterm a jehož implementaci najdete například v administraci VirtualMaster.cz.

Omezení takového řešení jsou jasná, pouze textová konzole a potřeba konkrétní konfigurace virtuálního serveru, aby byl dostupný jak GRUB/LILO, tak přístup do systému. Mnohem pohodlněji se jeví využití VNC serveru, který umí libvirt nahodit pro každý virtuální stroj. K němu se lze připojit například přes SSH, ale protože poslouchá na standardním TCP portu, je možné ho protunelovat vším možným.

VNC server je řešením jen půlky problému. Druhou je VNC klient, který by běžel v prohlížeči a nepoužíval žádný speciální plugin. Naštěstí implementace HTML5 v moderních prohlížečích postoupila natolik, že sen o VNC klientovi v prohlížeči proměnil ve skutečnost projekt noVNC.

Autorem noVNC je Joel Martin. Jeho projekt používá HTML5 technologie Canvas pro vykreslování a WebSocket pro spojení se serverem. Jak možná už tušíte, na jednom z vašich serverů musí běžet WebSocket proxy, která se postará o komunikaci mezi prohlížečem a TCP portem VNC serveru vašeho virtuálního stroje. Proxy musí běžet na takovém místě, ze které bude dostupná na internetu a zároveň uvidí na port VNC serveru.

Proti řešení s klientem napsaným v Javě je použití noVNC pro uživatele pohodlné hned z několik důvodu:

  • není třeba nic instalovat,
  • konzole serveru je dostupná na jedno kliknutí,
  • noVNC je snadno přizpůsobitelný,
  • a funguje i na mobilních zařízeních.

WebSocket proxy

Řešení s noVNC potřebuje proxy, která se postará o zabezpečení a vůbec o celé spojení mezi VNC serverem a klientem. Projekt noVNC, resp Joel Martin, přišel s vlastní proxy s názvem websockify. Ta je napsaná v Pythonu a podporuje vše, co noVNC potřebuje, včetně šifrování. Websockify je dostupné v repositáři PyPi, takže instalace je velmi jednoduchá:

# pip install websockify 

Nahození WebSocket-ready webserveru na portu 8000, který zprostředkovává komunikaci s portem VNC serveru na 127.0.0.1:5900, je podobně jednoduché:

# websockify  8000 localhost:5900 

V případě, že chcete, aby websockify běželo na pozadí, stačí přidat parametr -D.

V reálném světě ale musíme předpokládat, že někdo může po cestě poslouchat, takže je třeba doplnit do komunikace mezi WebSocket proxy a noVNC šifrování. K tomu nám pomůže SSL certifikát, který buď už máme nebo si vytvoříme vlastní.

# openssl req -new -x509 -days 365 -nodes -out websock.pem -keyout websock.pem 

Chrome i Firefox neumí WebSocket přes SSL/TLS s certifikátem, kterému nedůvěřují. Buď tedy vytvořený certifikát importujte do svého prohlížeče nebo použijte například službu StartSSL, kde je možné vygenerovat zdarma jednoduchý a důvěryhodný SSL certifikát. Pokud vám SSL certifkáty nejsou úplně jasné, podívejte se na náš seriál Jak na OpenSSL.

To by mělo být vše. Konečná konfigurace websockify vypadá takto:

# websockify 8000 --target-config=/root/token.cfg --cert=websock.pem 

NoVNC

Když běží proxy, můžeme se s ní připojit. Není na tom nic složitého, protože noVNC běží v prohlížeči, takže stačí přejít na novnc.com, kde si nakonfigurujete připojení na vaši WebProxy. V mém případě vypadá konfigurace takto:

Parametr Path je velmi důležitý, protože v něm je proměnná token, kterou vybíráme kam se má proxy připojit. Token můžeme použít i jako zabezpečení místo hesla, stačí ke každému spojení vygenerovat unikátní identifikátor UUID a SSL připojení se postará o to, aby byl schovaný před zraky zákeřných hackerů na trase mezi uživatelem a proxy.

Když se vám vše podaří, vypadá výsledek následovně:

Původní myšlenka byla ovšem integrace noVNC do existujícího projektu. K tomu existuje v dokumentaci k noVNC návod, nicméně není aktuální a nefunguje. Po krátkém průzkumu zdrojového kódu se mi podařilo dát dohromady funkční postup. V prvním kroku je třeba stáhnout zdrojové kódy noVNC a nakopírovat adresář include někam, kde ho uvidí váš projekt. V něm máte už určitě připravené místo, na které stačí nakopírovat následující kód:

bitcoin_skoleni

<div id="noVNC_screen">
    <div id="noVNC_status_bar" class="noVNC_status_bar" style="margin-top: 0px;">
        <table border=0 width="100%"><tr>
            <td><div id="noVNC_status">Loading</div></td>
            <td width="1%"><div id="noVNC_buttons">
                <input type=button value="Send CtrlAltDel" id="sendCtrlAltDelButton">
            </div></td>
        </tr></table>
    </div>
    <canvas id="noVNC_canvas" width="640px" height="480px" class="vnc">
        Canvas not supported.
    </canvas>
</div>

<script>
    /*jslint white: false */
    /*global window, $, Util, RFB, */
    "use strict";

    // Load supporting scripts
    Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js",
        "input.js", "display.js", "jsunzip.js", "rfb.js"]);

    var rfb;

    function passwordRequired(rfb) {
        var msg;
        msg = '<form onsubmit="return setPassword();"';
        msg += '  style="margin-bottom: 0px">';
        msg += 'Password Required: ';
        msg += '<input type=password size=10 id="password_input" class="noVNC_status">';
        msg += '<\/form>';
        $D('noVNC_status_bar').setAttribute("class", "noVNC_status_warn");
        $D('noVNC_status').innerHTML = msg;
    }
    function setPassword() {
        rfb.sendPassword($D('password_input').value);
        return false;
    }
    function sendCtrlAltDel() {
        rfb.sendCtrlAltDel();
        return false;
    }
    function updateState(rfb, state, oldstate, msg) {
        var s, sb, cad, level;
        s = $D('noVNC_status');
        sb = $D('noVNC_status_bar');
        cad = $D('sendCtrlAltDelButton');
        switch (state) {
            case 'failed':       level = "error";  break;
            case 'fatal':        level = "error";  break;
            case 'normal':       level = "normal"; break;
            case 'disconnected': level = "normal"; break;
            case 'loaded':       level = "normal"; break;
            default:             level = "warn";   break;
        }

        if (state === "normal") { cad.disabled = false; }
        else                    { cad.disabled = true; }

        if (typeof(msg) !== 'undefined') {
            sb.setAttribute("class", "noVNC_status_" + level);
            s.innerHTML = msg;
        }
    }

    window.onscriptsload = function () {
        var host, port, password, path, token;

        $D('sendCtrlAltDelButton').style.display = "inline";
        $D('sendCtrlAltDelButton').onclick = sendCtrlAltDel;

        WebUtil.init_logging(WebUtil.getQueryVar('logging', 'warn'));
        document.title = unescape(WebUtil.getQueryVar('title', 'noVNC'));
        // By default, use the host and port of server that served this file
        host = "192.168.1.99";
        port = 8001;

        // If a token variable is passed in, set the parameter in a cookie.
        // This is used by nova-novncproxy.
        token = WebUtil.getQueryVar('token', null);
        if (token) {
            WebUtil.createCookie('token', token, 1)
        }

        password = WebUtil.getQueryVar('password', '');
        path = WebUtil.getQueryVar('path', 'websockify');

        if ((!host) || (!port)) {
            updateState('failed',
                    "Must specify host and port in URL");
            return;
        }

        rfb = new RFB({'target':       $D('noVNC_canvas'),
            'encrypt':      WebUtil.getQueryVar('encrypt',
                    (window.location.protocol === "https:")),
            'repeaterID':   WebUtil.getQueryVar('repeaterID', ''),
            'true_color':   WebUtil.getQueryVar('true_color', true),
            'local_cursor': WebUtil.getQueryVar('cursor', true),
            'shared':       WebUtil.getQueryVar('shared', true),
            'view_only':    WebUtil.getQueryVar('view_only', false),
            'updateState':  updateState,
            'onPasswordRequired':  passwordRequired});
        rfb.connect(host, port, password, path);

        var display = rfb.get_display();
        display.resize(640, 480);
    };
</script>

Pokud máte správně nastavené cesty, adresu serveru, port a případně path, tak se na jeho místě objeví VNC konzole. Výsledek si můžete prohlédnout na screenshotu níže.

Shrnutí

Projekt noVNC není nic, co byste mohli využít každý den, ale je možné, že na něj narazíte při využívání služeb různých poskytovatelů virtuálních serverů. Měli byste vědět, že noVNC umí komunikovat zabezpečeně a když ho chcete použít, stačí vám pouze moderní webový prohlížeč. Stejně dobře jako v Chromu na screenshotech v tomto článku funguje noVNC také na mobilních zařízeních jako jsou telefony či tablety.

Autor článku

Adam Štrauch je redaktorem serveru Root.cz a svobodný software nasazuje jak na desktopech tak i na routerech a serverech. Ve svém volném čase se stará o komunitní síť, ve které je již přes 100 členů.