diff --git a/proxstar/__init__.py b/proxstar/__init__.py index 36ba277..b903c2d 100644 --- a/proxstar/__init__.py +++ b/proxstar/__init__.py @@ -42,6 +42,7 @@ from proxstar.vnc import ( get_vnc_targets, delete_vnc_target, stop_websockify, + open_vnc_session, ) from proxstar.auth import get_auth from proxstar.util import gen_password @@ -286,17 +287,12 @@ def vm_console(vmid): if user.rtp or int(vmid) in user.allowed_vms: # import pdb; pdb.set_trace() vm = VM(vmid) - stop_ssh_tunnel(vm.id, ssh_tunnels) - port = str(5900 + int(vmid)) - token = add_vnc_target(port) - node = '{}.csh.rit.edu'.format(vm.node) - logging.info('creating SSH tunnel to %s for VM %s', node, vm.id) - tunnel = start_ssh_tunnel(node, port) - vm.configure_vnc_in_vm_config(app.config['PROXMOX_SSH_USER'], app.config['PROXMOX_SSH_KEY_PASS']) - ssh_tunnels.append(tunnel) - # vm.start_vnc(port) # Broken :( - # return json.dumps([app.config['VNC_HOST'], token]), 200 - return {'host' : app.config['VNC_HOST'], 'token' : token}, 200 + vnc_ticket, vnc_port = open_vnc_session(vmid, vm.node, app.config['PROXMOX_USER'], app.config['PROXMOX_PASS']) + node = f'proxmox01-nrh.csh.rit.edu' + token = add_vnc_target(node, vnc_port) + # return {'host' : node, 'port' : vnc_port, 'token' : token, 'password' : vnc_ticket}, 200 + return {'host' : app.config['VNC_HOST'], 'port' : 8081, 'token' : token, 'password' : vnc_ticket}, 200 + else: return '', 403 diff --git a/proxstar/static/js/script.js b/proxstar/static/js/script.js index aebb86e..53bd1cd 100644 --- a/proxstar/static/js/script.js +++ b/proxstar/static/js/script.js @@ -654,7 +654,7 @@ $("#console-vm").click(function(){ }).then((vnc_params) => { // TODO (willnilges): encrypt=true // TODO (willnilges): set host and port to an env variable - window.open(`/static/noVNC/vnc.html?autoconnect=true&password=${vmid}&host=${vnc_params.host}&port=8081&path=path?token=${vnc_params.token}`, '_blank'); + window.open(`/static/noVNC/vnc.html?autoconnect=true&password=${vnc_params.password}&host=${vnc_params.host}&port=${vnc_params.port}&path=path?token=${vnc_params.token}`, '_blank'); }).catch(err => { if (err) { swal("Uh oh...", `Unable to start console for ${vmname}. Please try again later.`, "error"); diff --git a/proxstar/vm.py b/proxstar/vm.py index a7efca1..f7ce8bf 100644 --- a/proxstar/vm.py +++ b/proxstar/vm.py @@ -274,9 +274,7 @@ class VM: def configure_vnc_in_vm_config(self, ssh_user, ssh_pass): """ Sets the vm up for VNC. Enables it to open a socket on localhost with a pre-determined password, which proxstar can then proxy to a noVNC - instance. - - TODO (willnilges): Current password is "chomchom1", but should be changed lol + instance. """ # proxmox = connect_proxmox() config = f'args: -object secret,id=secvnc{self.id},data={self.id} -vnc 127.0.0.1:{int(self.id)+5900},password-secret=secvnc{self.id}' diff --git a/proxstar/vnc.py b/proxstar/vnc.py index dd1562f..5b48654 100644 --- a/proxstar/vnc.py +++ b/proxstar/vnc.py @@ -5,6 +5,7 @@ import time import requests from flask import current_app as app from sshtunnel import SSHTunnelForwarder +import urllib.parse from proxstar import logging from proxstar.util import gen_password @@ -31,36 +32,69 @@ def get_vnc_targets(): target_dict = {} values = line.strip().split(':') target_dict['token'] = values[0] - target_dict['port'] = values[2] + target_dict['host'] = values[1] + values[2] targets.append(target_dict) target_file.close() return targets - -def add_vnc_target(port): +def add_vnc_target(node, port): # TODO (willnilges): This doesn't throw an error if the target file is wrong. targets = get_vnc_targets() - target = next((target for target in targets if target['port'] == port), None) + target = next((target for target in targets if target['host'] == f'{node}:{port}'), None) if target: return target['token'] else: target_file = open(app.config['WEBSOCKIFY_TARGET_FILE'], 'a') token = gen_password(32, 'abcdefghijklmnopqrstuvwxyz0123456789') - target_file.write('{}: 127.0.0.1:{}\n'.format(token, str(port))) + target_file.write(f'{token}: {node}:{port}\n') target_file.close() return token - -def delete_vnc_target(port): +def delete_vnc_target(node, port): targets = get_vnc_targets() - target = next((target for target in targets if target['port'] == str(port)), None) + target = next((target for target in targets if target['host'] == f'{node}:{port}'), None) if target: targets.remove(target) target_file = open(app.config['WEBSOCKIFY_TARGET_FILE'], 'w') for target in targets: - target_file.write('{}: 127.0.0.1:{}\n'.format(target['token'], target['port'])) + target_file.write(f"{target['token']}: {target['host']}\n") target_file.close() +def open_vnc_session(vmid, node, proxmox_user, proxmox_pass): + """ Pings the Proxmox API to request a VNC Proxy connection. Authenticates + against the API using a Uname/Pass, gets a few tokens back, then uses those + tokens to open the VNC Proxy. Use these to connect to the VM's host with + websockify proxy. + Returns: Ticket to use as the noVNC password, and a port. + """ + # Get Proxmox API ticket and CSRF_Prevention_Token + # TODO: Use Proxmoxer to get this information? + # TODO: Report errors!? + data = {"username": proxmox_user, "password": proxmox_pass} + response_data = requests.post( + f"https://{node}.csh.rit.edu:8006/" + "api2/json/access/ticket", + verify=False, + data=data, + ).json()["data"] + if response_data is None: + raise AuthenticationError( + "Could not authenticate against `ticket` endpoint! Check uname/password" + ) + csrf_prevention_token = response_data['CSRFPreventionToken'] + ticket = response_data['ticket'] + + proxy_params = {"node": node, "vmid": str(vmid), "websocket": '1', "generate-password": '0'} + + vncproxy_response_data = requests.post( + "https://proxmox01-nrh.csh.rit.edu:8006" + f"/api2/json/nodes/{node}/qemu/{vmid}/vncproxy", + verify=False, + timeout=5, + params=proxy_params, + headers={"CSRFPreventionToken": csrf_prevention_token}, + cookies={"PVEAuthCookie": ticket} + ).json()["data"] + + return urllib.parse.quote_plus(vncproxy_response_data['ticket']), vncproxy_response_data['port'] def start_ssh_tunnel(node, port): """Forwards a port on a node