This commit is contained in:
Will Nilges 2022-07-10 18:06:03 -04:00
parent 1134939d3c
commit 98d1408693
4 changed files with 54 additions and 13 deletions

View file

@ -17,6 +17,13 @@ It is available to house members at [proxstar.csh.rit.edu](https://proxstar.csh.
1. [Fork](https://help.github.com/en/articles/fork-a-repo) this repository 1. [Fork](https://help.github.com/en/articles/fork-a-repo) this repository
- Optionally create a new [git branch](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell) if your change is more than a small tweak (`git checkout -b BRANCH-NAME-HERE`) - Optionally create a new [git branch](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell) if your change is more than a small tweak (`git checkout -b BRANCH-NAME-HERE`)
2. Create a Virtualenv to do your work in.
```
mkdir venv
python3.8 -m venv venv
source venv/bin/activate
```
3. Make your changes locally, commit, and push to your fork 3. Make your changes locally, commit, and push to your fork
- If you want to test locally, you should copy `config.py` to `config_local.py`, and talk to an RTP about filling in secrets. - If you want to test locally, you should copy `config.py` to `config_local.py`, and talk to an RTP about filling in secrets.
- Lint and format your local changes with `pylint proxstar` and `black proxstar` - Lint and format your local changes with `pylint proxstar` and `black proxstar`

View file

@ -13,7 +13,17 @@ from redis import Redis
from rq_scheduler import Scheduler from rq_scheduler import Scheduler
from sqlalchemy import create_engine from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
from flask import Flask, render_template, request, redirect, session, abort, url_for, jsonify, Response from flask import (
Flask,
render_template,
request,
redirect,
session,
abort,
url_for,
jsonify,
Response,
)
import sentry_sdk import sentry_sdk
from sentry_sdk.integrations.flask import FlaskIntegration from sentry_sdk.integrations.flask import FlaskIntegration
from sentry_sdk.integrations.rq import RqIntegration from sentry_sdk.integrations.rq import RqIntegration
@ -135,6 +145,7 @@ if 'cleanup_vnc' not in scheduler:
interval=3600, interval=3600,
) )
def add_rq_dashboard_auth(blueprint): def add_rq_dashboard_auth(blueprint):
@blueprint.before_request @blueprint.before_request
@auth.oidc_auth @auth.oidc_auth
@ -159,6 +170,7 @@ def forbidden(e):
user = User(session['userinfo']['preferred_username']) user = User(session['userinfo']['preferred_username'])
return render_template('403.html', user=user, e=e), 403 return render_template('403.html', user=user, e=e), 403
@app.route('/') @app.route('/')
@app.route('/user/<string:user_view>') @app.route('/user/<string:user_view>')
@auth.oidc_auth @auth.oidc_auth
@ -287,15 +299,23 @@ def vm_console(vmid):
if user.rtp or int(vmid) in user.allowed_vms: if user.rtp or int(vmid) in user.allowed_vms:
# import pdb; pdb.set_trace() # import pdb; pdb.set_trace()
vm = VM(vmid) vm = VM(vmid)
vnc_ticket, vnc_port = open_vnc_session(vmid, vm.node, app.config['PROXMOX_USER'], app.config['PROXMOX_PASS']) vnc_ticket, vnc_port = open_vnc_session(
vmid, vm.node, app.config['PROXMOX_USER'], app.config['PROXMOX_PASS']
)
node = f'{vm.node}.csh.rit.edu' node = f'{vm.node}.csh.rit.edu'
token = add_vnc_target(node, vnc_port) token = add_vnc_target(node, vnc_port)
# return {'host' : node, 'port' : vnc_port, 'token' : token, 'password' : vnc_ticket}, 200 # 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 return {
'host': app.config['VNC_HOST'],
'port': 8081,
'token': token,
'password': vnc_ticket,
}, 200
else: else:
return '', 403 return '', 403
@app.route('/vm/<string:vmid>/cpu/<int:cores>', methods=['POST']) @app.route('/vm/<string:vmid>/cpu/<int:cores>', methods=['POST'])
@auth.oidc_auth @auth.oidc_auth
def vm_cpu(vmid, cores): def vm_cpu(vmid, cores):

View file

@ -272,19 +272,28 @@ class VM:
# ) # )
def configure_vnc_in_vm_config(self, ssh_user, ssh_pass): 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 """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 with a pre-determined password, which proxstar can then proxy to a noVNC
instance. instance.
""" """
# proxmox = connect_proxmox() # 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}' 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}'
path = f'/etc/pve/local/qemu-server/{self.id}.conf' path = f'/etc/pve/local/qemu-server/{self.id}.conf'
with paramiko.SSHClient() as ssh: with paramiko.SSHClient() as ssh:
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(self.node, port=22, username=ssh_user, key_filename='proxmox_ssh_key', passphrase=ssh_pass) ssh.connect(
ssh.exec_command(f"if grep -- '{config}' {path}; then echo identical config found; else sed -i /dev/null '/-vnc/d' {path}") #YOLO self.node,
ssh.exec_command(f"if grep -- '-vnc' {path}; then echo found config; else echo {config} >> {path}; fi") port=22,
username=ssh_user,
key_filename='proxmox_ssh_key',
passphrase=ssh_pass,
)
ssh.exec_command(
f"if grep -- '{config}' {path}; then echo identical config found; else sed -i /dev/null '/-vnc/d' {path}"
) # YOLO
ssh.exec_command(
f"if grep -- '-vnc' {path}; then echo found config; else echo {config} >> {path}; fi"
)
@retry(wait=wait_fixed(2), stop=stop_after_attempt(5)) @retry(wait=wait_fixed(2), stop=stop_after_attempt(5))
def eject_iso(self): def eject_iso(self):

View file

@ -37,6 +37,7 @@ def get_vnc_targets():
target_file.close() target_file.close()
return targets return targets
def add_vnc_target(node, port): def add_vnc_target(node, port):
# TODO (willnilges): This doesn't throw an error if the target file is wrong. # TODO (willnilges): This doesn't throw an error if the target file is wrong.
# TODO (willnilges): This will duplicate targets # TODO (willnilges): This will duplicate targets
@ -52,6 +53,7 @@ def add_vnc_target(node, port):
target_file.close() target_file.close()
return token return token
def delete_vnc_target(node, port): def delete_vnc_target(node, port):
targets = get_vnc_targets() targets = get_vnc_targets()
target = next((target for target in targets if target['host'] == f'{node}:{port}'), None) target = next((target for target in targets if target['host'] == f'{node}:{port}'), None)
@ -62,8 +64,9 @@ def delete_vnc_target(node, port):
target_file.write(f"{target['token']}: {target['host']}\n") target_file.write(f"{target['token']}: {target['host']}\n")
target_file.close() target_file.close()
def open_vnc_session(vmid, node, proxmox_user, proxmox_pass): def open_vnc_session(vmid, node, proxmox_user, proxmox_pass):
""" Pings the Proxmox API to request a VNC Proxy connection. Authenticates """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 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 tokens to open the VNC Proxy. Use these to connect to the VM's host with
websockify proxy. websockify proxy.
@ -91,17 +94,18 @@ def open_vnc_session(vmid, node, proxmox_user, proxmox_pass):
timeout=5, timeout=5,
params=proxy_params, params=proxy_params,
headers={"CSRFPreventionToken": csrf_prevention_token}, headers={"CSRFPreventionToken": csrf_prevention_token},
cookies={"PVEAuthCookie": ticket} cookies={"PVEAuthCookie": ticket},
).json()["data"] ).json()["data"]
return urllib.parse.quote_plus(vncproxy_response_data['ticket']), vncproxy_response_data['port'] return urllib.parse.quote_plus(vncproxy_response_data['ticket']), vncproxy_response_data['port']
def start_ssh_tunnel(node, port): def start_ssh_tunnel(node, port):
"""Forwards a port on a node """Forwards a port on a node
to the proxstar container to the proxstar container
""" """
port = int(port) port = int(port)
server = SSHTunnelForwarder( server = SSHTunnelForwarder(
node, node,
ssh_username=app.config['PROXMOX_SSH_USER'], ssh_username=app.config['PROXMOX_SSH_USER'],
@ -113,6 +117,7 @@ def start_ssh_tunnel(node, port):
server.start() server.start()
return server return server
def stop_ssh_tunnel(vmid, ssh_tunnels): def stop_ssh_tunnel(vmid, ssh_tunnels):
# Tear down the SSH tunnel and VNC target entry for a given VM # Tear down the SSH tunnel and VNC target entry for a given VM
port = 5900 + int(vmid) port = 5900 + int(vmid)