mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-09 03:36:48 +00:00
- Computes API: mark password as write_only in ComputeSerializer, restrict ComputeViewSet and architectures to IsAdminUser. - Instances API: enforce user ownership tenant isolation in InstanceViewSet.list, restrict MigrateViewSet and CreateInstanceViewSet to IsAdminUser, require IsAdminUser for mutating FlavorViewSet. - Storage, Network, and Interface APIs: restrict StorageViewSet, VolumeViewSet, NetworkViewSet, InterfaceViewSet to IsAdminUser. - AppSettings & NWFilters: protect appsettings and nwfilter views with @superuser_only (403 Forbidden for non-superusers). - noVNC Console: sign tokens with TimestampSigner (salt='console.novnc', max_age=600), verify signature and user authorization in novncd with backward-compatible fallback for debug/test mode. - Accounts: avoid hardcoded default admin password; generate cryptographically secure random password unless ADMIN_PASSWORD is set (preserve 'admin' in test runner). - gstfsd daemon: bind to 127.0.0.1 by default instead of 0.0.0.0. - Tests: added comprehensive test coverage for API security, token expiration/rejection, and view permissions (132 tests passing).
125 lines
4.8 KiB
Python
125 lines
4.8 KiB
Python
from unittest.mock import patch
|
|
from accounts.models import UserInstance
|
|
from computes.api.serializers import ComputeSerializer
|
|
from computes.models import Compute
|
|
from django.contrib.auth import get_user_model
|
|
from django.test import TestCase
|
|
from instances.models import Flavor, Instance
|
|
from rest_framework.test import APIClient
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
class APISecurityTestCase(TestCase):
|
|
def setUp(self):
|
|
self.client = APIClient()
|
|
self.admin = User.objects.create_superuser(
|
|
username="admin_api_test",
|
|
email="admin_api@example.com",
|
|
password="adminpassword",
|
|
)
|
|
self.regular_user = User.objects.create_user(
|
|
username="regular_api_user",
|
|
email="user@example.com",
|
|
password="userpassword",
|
|
)
|
|
self.compute = Compute.objects.create(
|
|
name="api-test-compute",
|
|
hostname="127.0.0.1",
|
|
login="libvirt_user",
|
|
password="secret_hypervisor_password",
|
|
type=1,
|
|
)
|
|
self.flavor = Flavor.objects.create(
|
|
label="test-flavor",
|
|
vcpu=2,
|
|
memory=2048,
|
|
disk=20,
|
|
)
|
|
self.instance1 = Instance.objects.create(
|
|
compute=self.compute,
|
|
name="inst-user-owned",
|
|
uuid="11111111-1111-1111-1111-111111111111",
|
|
)
|
|
self.instance2 = Instance.objects.create(
|
|
compute=self.compute,
|
|
name="inst-other-owned",
|
|
uuid="22222222-2222-2222-2222-222222222222",
|
|
)
|
|
# Assign instance1 to regular_user
|
|
UserInstance.objects.create(user=self.regular_user, instance=self.instance1)
|
|
|
|
def test_compute_serializer_password_is_write_only(self):
|
|
serializer = ComputeSerializer(instance=self.compute)
|
|
self.assertNotIn("password", serializer.data)
|
|
self.assertEqual(serializer.data["name"], "api-test-compute")
|
|
self.assertEqual(serializer.data["login"], "libvirt_user")
|
|
|
|
def test_compute_viewset_permissions(self):
|
|
# Anonymous - redirected to login by LoginRequiredMiddleware
|
|
res = self.client.get("/api/v1/computes/")
|
|
self.assertIn(res.status_code, [302, 401, 403])
|
|
|
|
# Regular user - forbidden by IsAdminUser
|
|
self.client.force_login(self.regular_user)
|
|
res = self.client.get("/api/v1/computes/")
|
|
self.assertEqual(res.status_code, 403)
|
|
|
|
# Admin user - allowed
|
|
self.client.force_login(self.admin)
|
|
res = self.client.get("/api/v1/computes/")
|
|
self.assertEqual(res.status_code, 200)
|
|
|
|
@patch("computes.utils.refresh_instance_database")
|
|
def test_compute_instance_list_isolation(self, mock_refresh):
|
|
# Regular user should only see instance1
|
|
self.client.force_login(self.regular_user)
|
|
res = self.client.get(f"/api/v1/computes/{self.compute.id}/instances/")
|
|
self.assertEqual(res.status_code, 200)
|
|
names = [item["name"] for item in res.data]
|
|
self.assertIn("inst-user-owned", names)
|
|
self.assertNotIn("inst-other-owned", names)
|
|
|
|
# Admin user should see both instances
|
|
self.client.force_login(self.admin)
|
|
res = self.client.get(f"/api/v1/computes/{self.compute.id}/instances/")
|
|
self.assertEqual(res.status_code, 200)
|
|
names = [item["name"] for item in res.data]
|
|
self.assertIn("inst-user-owned", names)
|
|
self.assertIn("inst-other-owned", names)
|
|
|
|
def test_flavor_permissions(self):
|
|
# Regular user can read flavors
|
|
self.client.force_login(self.regular_user)
|
|
res = self.client.get("/api/v1/flavor/")
|
|
self.assertEqual(res.status_code, 200)
|
|
|
|
# Regular user cannot mutate flavors
|
|
res = self.client.post(
|
|
"/api/v1/flavor/",
|
|
{"label": "malicious", "vcpu": 4, "memory": 4096, "disk": 40},
|
|
)
|
|
self.assertEqual(res.status_code, 403)
|
|
|
|
res = self.client.delete(f"/api/v1/flavor/{self.flavor.id}/")
|
|
self.assertEqual(res.status_code, 403)
|
|
|
|
def test_migrate_viewset_permissions(self):
|
|
self.client.force_login(self.regular_user)
|
|
res = self.client.post("/api/v1/migrate/", {})
|
|
self.assertEqual(res.status_code, 403)
|
|
|
|
def test_network_viewset_permissions(self):
|
|
self.client.force_login(self.regular_user)
|
|
res = self.client.get(f"/api/v1/computes/{self.compute.id}/networks/")
|
|
self.assertEqual(res.status_code, 403)
|
|
|
|
def test_interface_viewset_permissions(self):
|
|
self.client.force_login(self.regular_user)
|
|
res = self.client.get(f"/api/v1/computes/{self.compute.id}/interfaces/")
|
|
self.assertEqual(res.status_code, 403)
|
|
|
|
def test_storage_viewset_permissions(self):
|
|
self.client.force_login(self.regular_user)
|
|
res = self.client.get(f"/api/v1/computes/{self.compute.id}/storages/")
|
|
self.assertEqual(res.status_code, 403)
|