mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-11 12:46:49 +00:00
feat(security): apply Codex review hardening to console tokens and superuser authorization
This commit is contained in:
parent
1227674d8f
commit
708b986e8e
11 changed files with 168 additions and 26 deletions
|
|
@ -99,7 +99,7 @@ def get_connection_infos(token):
|
|||
|
||||
signer = TimestampSigner(salt="console.novnc")
|
||||
try:
|
||||
unsigned = signer.unsign(token, max_age=600)
|
||||
unsigned = signer.unsign(token, max_age=120)
|
||||
parts = unsigned.split(":")
|
||||
if len(parts) >= 2:
|
||||
host = int(parts[0])
|
||||
|
|
@ -107,7 +107,7 @@ def get_connection_infos(token):
|
|||
if len(parts) >= 3:
|
||||
user_id = int(parts[2])
|
||||
except (BadSignature, SignatureExpired, ValueError, TypeError):
|
||||
if getattr(settings, "DEBUG", False) or "test" in sys.argv:
|
||||
if "test" in sys.argv:
|
||||
try:
|
||||
temptoken = token.split("-", 1)
|
||||
host = int(temptoken[0])
|
||||
|
|
@ -126,6 +126,8 @@ def get_connection_infos(token):
|
|||
User = get_user_model()
|
||||
try:
|
||||
user = User.objects.get(id=user_id)
|
||||
if not user.is_active:
|
||||
raise PermissionError("User account is inactive or disabled")
|
||||
if not user.is_superuser and not user.has_perm("instances.view_instances"):
|
||||
if not UserInstance.objects.filter(instance=instance, user=user).exists():
|
||||
raise PermissionError("User no longer has access to this instance")
|
||||
|
|
@ -151,9 +153,7 @@ def get_connection_infos(token):
|
|||
console_port = conn.get_console_port()
|
||||
console_socket = conn.get_console_socket()
|
||||
except Exception as e:
|
||||
logging.error(
|
||||
"Fail to retrieve console connection infos for token %s : %s" % (token, e)
|
||||
)
|
||||
logging.error("Fail to retrieve console connection infos: %s", e)
|
||||
raise
|
||||
return (
|
||||
connhost,
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ class ConsoleViewsTestCase(TestCase):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, "console-vnc-lite.html")
|
||||
signer = TimestampSigner(salt="console.novnc")
|
||||
unsigned = signer.unsign(response.cookies["token"].value, max_age=600)
|
||||
unsigned = signer.unsign(response.cookies["token"].value, max_age=120)
|
||||
self.assertEqual(
|
||||
unsigned, f"{self.compute.id}:{self.instance.uuid}:{self.admin_user.id}"
|
||||
)
|
||||
|
|
@ -521,3 +521,63 @@ class NovncdDaemonLogicTestCase(TestCase):
|
|||
|
||||
with self.assertRaises(PermissionError):
|
||||
novncd_mod.get_connection_infos(token)
|
||||
|
||||
def test_get_connection_infos_inactive_user(self):
|
||||
User = get_user_model()
|
||||
inactive_user = User.objects.create_user(
|
||||
username="inactive_console_user",
|
||||
password="password",
|
||||
is_active=False,
|
||||
)
|
||||
compute = Compute.objects.create(
|
||||
name="novnc-inactive-compute",
|
||||
hostname="127.0.0.1",
|
||||
login="root",
|
||||
password="",
|
||||
type=1,
|
||||
)
|
||||
instance = Instance.objects.create(
|
||||
compute=compute,
|
||||
name="novnc-inactive-vm",
|
||||
uuid="44444444-4444-4444-4444-444444444444",
|
||||
)
|
||||
UserInstance.objects.create(instance=instance, user=inactive_user)
|
||||
|
||||
signer = TimestampSigner(salt="console.novnc")
|
||||
token = signer.sign(f"{compute.id}:{instance.uuid}:{inactive_user.id}")
|
||||
|
||||
with self.assertRaises(PermissionError):
|
||||
novncd_mod.get_connection_infos(token)
|
||||
|
||||
@patch("sys.argv", ["novncd"])
|
||||
@override_settings(DEBUG=True)
|
||||
def test_get_connection_infos_debug_true_does_not_bypass(self):
|
||||
with self.assertRaises(PermissionError):
|
||||
novncd_mod.get_connection_infos("invalid-token-in-debug")
|
||||
|
||||
@patch("sys.argv", ["novncd"])
|
||||
def test_get_connection_infos_expired_token(self):
|
||||
User = get_user_model()
|
||||
user = User.objects.filter(is_superuser=True).first()
|
||||
compute = Compute.objects.create(
|
||||
name="novnc-expired-compute",
|
||||
hostname="127.0.0.1",
|
||||
login="root",
|
||||
password="",
|
||||
type=1,
|
||||
)
|
||||
instance = Instance.objects.create(
|
||||
compute=compute,
|
||||
name="novnc-expired-vm",
|
||||
uuid="55555555-5555-5555-5555-555555555555",
|
||||
)
|
||||
|
||||
import time
|
||||
from unittest.mock import patch
|
||||
# Sign token 300 seconds in the past (> 120s TTL)
|
||||
with patch("time.time", return_value=time.time() - 300):
|
||||
signer = TimestampSigner(salt="console.novnc")
|
||||
expired_token = signer.sign(f"{compute.id}:{instance.uuid}:{user.id}")
|
||||
|
||||
with self.assertRaises(PermissionError):
|
||||
novncd_mod.get_connection_infos(expired_token)
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ def console(request):
|
|||
|
||||
if token:
|
||||
try:
|
||||
unsigned = signer.unsign(token, max_age=600)
|
||||
unsigned = signer.unsign(token, max_age=120)
|
||||
parts = unsigned.split(":")
|
||||
if len(parts) >= 2:
|
||||
host = int(parts[0])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue