1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 11:16:47 +00:00
webvirtcloud/instances/test_root_pass.py
catborise 7286ba079d fix(gstfsd): random password salt, timeouts, loopback-only daemon
- Hash the root password with a random SHA-512 salt instead of the fixed
  "$6$kgPoiREy".
- Talk to gstfsd through one helper with a 120 s socket timeout; an
  unreachable daemon or a bad reply is an error message, not a 500.
- gstfsd refuses to listen on a non-loopback address unless
  GSTFSD_ALLOW_REMOTE=1: it is an unauthenticated root service.
- gstfsd: encode replies (sendall(str) raised TypeError on Python 3, so
  the success path never worked) and use octal chmod modes.
- README: what gstfsd does and how to allow it on a remote compute.
2026-10-02 22:52:09 +03:00

108 lines
4.3 KiB
Python

import json
import socket
from unittest.mock import MagicMock, patch
from django.test import TestCase
from django.urls import reverse
from computes.models import Compute
from instances.models import Instance
class SetRootPassTestCase(TestCase):
def setUp(self):
self.client.login(username="admin", password="admin")
self.compute = Compute.objects.create(
name="test_compute_root_pass",
hostname="localhost",
type=1,
)
self.instance = Instance.objects.create(
compute=self.compute,
name="root-pass-vm",
uuid="87654321-4321-4321-4321-210987654321",
)
@patch("socket.socket")
@patch.object(Instance, "proxy")
def test_set_root_pass_post(self, mock_proxy, mock_socket_cls):
mock_proxy.get_status.return_value = 5 # status: running
mock_sock = MagicMock()
mock_socket_cls.return_value = mock_sock
mock_sock.recv.return_value = json.dumps({"return": "success"}).encode()
response = self.client.post(
reverse("instances:rootpasswd", args=[self.instance.id]),
{"passwd": "newSecretPassword123"}
)
# Redirects back to referer or instance detail
self.assertEqual(response.status_code, 302)
# Check socket sent the command with hashed password
self.assertTrue(mock_sock.send.called)
sent_bytes = mock_sock.send.call_args[0][0]
data = json.loads(sent_bytes.decode())
self.assertEqual(data["action"], "password")
self.assertEqual(data["vname"], "root-pass-vm")
# SHA-512 crypt with a random salt (ROADMAP S-08), not the old fixed one
self.assertTrue(data["passwd"].startswith("$6$"))
self.assertFalse(data["passwd"].startswith("$6$kgPoiREy$"))
def test_set_root_pass_get_not_allowed(self):
response = self.client.get(reverse("instances:rootpasswd", args=[self.instance.id]))
self.assertEqual(response.status_code, 405)
def test_set_root_pass_not_found(self):
response = self.client.post(reverse("instances:rootpasswd", args=[99999]), {"passwd": "secret"})
self.assertEqual(response.status_code, 404)
@patch("socket.socket")
@patch.object(Instance, "proxy")
def test_same_password_gets_a_different_salt_each_time(self, mock_proxy, mock_socket_cls):
mock_proxy.get_status.return_value = 5
mock_sock = MagicMock()
mock_socket_cls.return_value = mock_sock
mock_sock.recv.return_value = json.dumps({"return": "success"}).encode()
url = reverse("instances:rootpasswd", args=[self.instance.id])
self.client.post(url, {"passwd": "samePassword1"})
self.client.post(url, {"passwd": "samePassword1"})
hashes = [json.loads(c[0][0].decode())["passwd"] for c in mock_sock.send.call_args_list]
self.assertEqual(len(hashes), 2)
self.assertNotEqual(hashes[0], hashes[1])
@patch("socket.socket")
@patch.object(Instance, "proxy")
def test_unreachable_gstfsd_is_an_error_message_not_a_crash(self, mock_proxy, mock_socket_cls):
mock_proxy.get_status.return_value = 5
mock_sock = MagicMock()
mock_socket_cls.return_value = mock_sock
mock_sock.connect.side_effect = socket.timeout("timed out")
response = self.client.post(
reverse("instances:rootpasswd", args=[self.instance.id]), {"passwd": "x"}
)
self.assertEqual(response.status_code, 302)
mock_sock.settimeout.assert_called()
@patch("socket.socket")
@patch.object(Instance, "proxy")
def test_add_public_key_unreachable_gstfsd_is_an_error_message(self, mock_proxy, mock_socket_cls):
from accounts.models import UserSSHKey
from django.contrib.auth import get_user_model
admin = get_user_model().objects.get(username="admin")
key = UserSSHKey.objects.create(user=admin, keyname="k", keypublic="ssh-ed25519 AAAA k")
mock_proxy.get_status.return_value = 5
mock_sock = MagicMock()
mock_socket_cls.return_value = mock_sock
mock_sock.connect.side_effect = ConnectionRefusedError()
response = self.client.post(
reverse("instances:add_public_key", args=[self.instance.id]), {"sshkeyid": key.id}
)
self.assertEqual(response.status_code, 302)
mock_sock.settimeout.assert_called()