mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
The VNC page's token travels in the websocket URL, so nginx's access log and anyone who saw the URL had it for 120 s, and it opened the console as a bearer token. novncd now also wants the session cookie of the token's user. Its refusals were a warning without a user or an address: they now name the client address (nginx sends X-Real-IP to novncd too) and leave a Logs row "VNC console refused: reason".
62 lines
2.8 KiB
Python
62 lines
2.8 KiB
Python
"""The console token: the console view signs "host:uuid:user_id:console type"
|
|
for the VM whose console it shows; the console daemons (novncd for vnc,
|
|
socketiod for pty) accept it for 120 s if that user may still open that VM's
|
|
console. novncd, whose token travels in the websocket URL (and so in logs),
|
|
also wants the session cookie of the token's user (session_user_id).
|
|
socketiod's token travels in the Socket.IO handshake, not the URL."""
|
|
|
|
from importlib import import_module
|
|
|
|
from django import db
|
|
from django.conf import settings
|
|
from django.contrib.auth import SESSION_KEY
|
|
from django.contrib.auth import get_user_model
|
|
from django.core.signing import BadSignature, SignatureExpired, TimestampSigner
|
|
from django.http import parse_cookie
|
|
|
|
from instances.models import Instance
|
|
from instances.utils import can_open_console
|
|
|
|
SIGNER = TimestampSigner(salt="console.novnc")
|
|
MAX_AGE = 120
|
|
ACCESS_CHECK = 10 # seconds between checks that an open console's user may still use it
|
|
|
|
|
|
def sign(compute_id, uuid, user_id, console_type):
|
|
return SIGNER.sign(f"{compute_id}:{uuid}:{user_id}:{console_type}")
|
|
|
|
|
|
def authorize(token, console_type):
|
|
"""The Instance the token opens and the user it was given to;
|
|
PermissionError otherwise, also for a token of another console type."""
|
|
try:
|
|
host, uuid, user_id, signed_type = SIGNER.unsign(token, max_age=MAX_AGE).split(":")
|
|
host, user_id = int(host), int(user_id)
|
|
except (BadSignature, SignatureExpired, ValueError, TypeError):
|
|
raise PermissionError("Invalid or expired console token")
|
|
if signed_type != console_type:
|
|
raise PermissionError(f"The token is for a {signed_type} console")
|
|
instance = Instance.objects.filter(compute_id=host, uuid=uuid).select_related("compute").first()
|
|
user = get_user_model().objects.filter(id=user_id, is_active=True).first()
|
|
if instance is None or user is None or not can_open_console(user, instance):
|
|
raise PermissionError("No access to this console")
|
|
return instance, user
|
|
|
|
|
|
def may_still_open(user_id, instance):
|
|
"""The user still may open the console: an owner may have been removed or
|
|
the user deactivated since the session started."""
|
|
try:
|
|
user = get_user_model().objects.filter(pk=user_id).first()
|
|
return user is not None and can_open_console(user, instance)
|
|
finally:
|
|
db.connection.close() # a session may last for days: no idle connection meanwhile
|
|
|
|
|
|
def session_user_id(cookie_header):
|
|
"""The id (a string) of the user signed in with the session cookie of a
|
|
request's Cookie header, or None."""
|
|
session_key = parse_cookie(cookie_header or "").get(settings.SESSION_COOKIE_NAME)
|
|
if not session_key:
|
|
return None
|
|
return import_module(settings.SESSION_ENGINE).SessionStore(session_key).get(SESSION_KEY)
|