diff --git a/app/models.py b/app/models.py index 8ad58ba67d2ff19a739da5c53d900f4b5eef0b7e..176ddbad1fa2cc676f89567df8d420cf47f28936 100644 --- a/app/models.py +++ b/app/models.py @@ -272,6 +272,32 @@ class User(db.Model, UserMixin): return False return str(network.domain) in self.csentry_domains + def can_create_vm(self, host): + """Return True if the user can create the VM + + - host.device_type shall be VirtualMachine + - admin users can create anything + - normal users must have access to the network to create VIOC + - normal users can only create a VM if the host is in one of the allowed domains + - LOGIN_DISABLED can be set to True to turn off authentication check when testing. + In this case, this function always returns True. + """ + if str(host.device_type) != "VirtualMachine": + return False + if current_app.config.get("LOGIN_DISABLED") or self.is_admin: + return True + if not self.has_access_to_network(host.main_network): + # True is already returned for admin users + return False + if host.is_ioc: + # VIOC can be created by anyone having access to the network + return True + # VM can only be created if the domain is allowed + return ( + str(host.main_interface.network.domain) + in current_app.config["ALLOWED_VM_CREATION_DOMAINS"] + ) + def favorite_attributes(self): """Return all user's favorite attributes""" favorites_list = [ diff --git a/app/network/views.py b/app/network/views.py index 88aaffe3adb94e103cf427e596948d79e7e4ce57..bfe70f2ad83e83734552ceba635b8d7e7a3bef99 100644 --- a/app/network/views.py +++ b/app/network/views.py @@ -178,8 +178,11 @@ def view_host(name): ) return redirect(url_for("task.view_task", id_=task.id)) else: - if not current_user.is_admin: - flash(f"Only admin users are allowed to create a VM!", "info") + if not current_user.can_create_vm(host): + flash( + f"You don't have the proper permissions to create this VM. Please contact an admin user.", + "warning", + ) return redirect(url_for("network.view_host", name=name)) else: task = tasks.trigger_vm_creation( diff --git a/app/settings.py b/app/settings.py index 094c8dd579762cd63b4bc2c11c9321407477a759..63e824f494039c828771db4531415adb94c9dae1 100644 --- a/app/settings.py +++ b/app/settings.py @@ -73,6 +73,8 @@ CSENTRY_DOMAINS_LDAP_GROUPS = { "tn.esss.lu.se": ["ICS Employees", "ICS Consultants"], "cslab.esss.lu.se": ["ICS Employees", "ICS Consultants"], } +# List of domains where users can create a VM +ALLOWED_VM_CREATION_DOMAINS = ["cslab.esss.lu.se"] NETWORK_DEFAULT_PREFIX = 24 # ICS Ids starting with this prefix are considered temporary and can be changed diff --git a/app/templates/network/view_host.html b/app/templates/network/view_host.html index 10a2208cd5646f0bf5fa4b54f441a2c3dea0e254..240ba550f6b066a55168a8eb9de2d6040269ae34 100644 --- a/app/templates/network/view_host.html +++ b/app/templates/network/view_host.html @@ -66,7 +66,7 @@ {% endif %} </dl> </div> - {% if host.device_type.name.startswith('Virtual') and current_user.is_admin %} + {% if host.device_type.name.startswith('Virtual') %} {% if host.is_ioc %} {% set vm_type = 'Virtual IOC' %} {% else %} diff --git a/docs/network.rst b/docs/network.rst index 656bd9f0af9bbe6a9516d5fb47a2bb59666eeb74..8fde5890b5c4f7c125d2c5ef1cbba2c13f2f9c08 100644 --- a/docs/network.rst +++ b/docs/network.rst @@ -123,8 +123,6 @@ See :ref:`task`. VM creation ----------- -This is currently restricted to admin users! - From the *View host* page, you can trigger the creation of a Virtual machine: .. image:: _static/create_vm.png @@ -144,6 +142,10 @@ The post install job name can be defined per domain in the **AWX_POST_INSTALL** Admin users can disable the post install job run by selecting the *Skip post install job* checkbox. This checkbox is only visible to admin users. +A VIOC can be created by any user who has access to the associated network/domain. +The same restriction applies to VMs but the associated domain must also be part of the **ALLOWED_VM_CREATION_DOMAINS** list (currently cslab.tn.esss.lu.se). +Please contact an admin user if you don't have the proper permissions. + Ansible inventory ----------------- diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py index a653fc2a844526944a454829c5426089955a82d5..7d7cc0006b3e49e0fcf7952b8cb68bee4b8150b0 100644 --- a/tests/functional/conftest.py +++ b/tests/functional/conftest.py @@ -59,6 +59,7 @@ def app(request): "foo.example.org": ["CSEntry User", "CSEntry Consultant"], }, "AWX_URL": "https://awx.example.org", + "ALLOWED_VM_CREATION_DOMAINS": ["lab.example.org"], } app = create_app(config=config) ctx = app.app_context() diff --git a/tests/functional/test_models.py b/tests/functional/test_models.py index ecfd31791fb2d11a8cf17dbe53c033a9e6176616..18c491c9188e903fe291f4de1e035c920f0be87e 100644 --- a/tests/functional/test_models.py +++ b/tests/functional/test_models.py @@ -77,6 +77,83 @@ def test_user_has_access_to_network(user_factory, domain_factory, network_factor assert user.has_access_to_network(None) +def test_user_can_create_vm( + user_factory, + domain_factory, + network_factory, + device_type_factory, + host_factory, + interface_factory, +): + virtualmachine = device_type_factory(name="VirtualMachine") + domain_prod = domain_factory(name="prod.example.org") + domain_lab = domain_factory(name="lab.example.org") + network_prod = network_factory(domain=domain_prod) + network_lab = network_factory(domain=domain_lab) + network_lab_admin = network_factory(domain=domain_lab, admin_only=True) + vm_prod = host_factory(device_type=virtualmachine) + interface_factory(name=vm_prod.name, host=vm_prod, network=network_prod) + vioc_prod = host_factory(device_type=virtualmachine, is_ioc=True) + interface_factory(name=vioc_prod.name, host=vioc_prod, network=network_prod) + vm_lab = host_factory(device_type=virtualmachine) + interface_factory(name=vm_lab.name, host=vm_lab, network=network_lab) + vioc_lab = host_factory(device_type=virtualmachine, is_ioc=True) + interface_factory(name=vioc_lab.name, host=vioc_lab, network=network_lab) + vm_lab_admin = host_factory(device_type=virtualmachine) + interface_factory( + name=vm_lab_admin.name, host=vm_lab_admin, network=network_lab_admin + ) + vioc_lab_admin = host_factory(device_type=virtualmachine, is_ioc=True) + interface_factory( + name=vioc_lab_admin.name, host=vioc_lab_admin, network=network_lab_admin + ) + non_vm = host_factory() + non_vm_ioc = host_factory(is_ioc=True) + interface_factory(name=non_vm.name, host=non_vm, network=network_lab) + interface_factory(name=non_vm_ioc.name, host=non_vm_ioc, network=network_lab) + # User has access to prod and lab networks but can only create a VM in the lab + # (due to ALLOWED_VM_CREATION_DOMAINS) and VIOC in both + user = user_factory(groups=["CSEntry Prod", "CSEntry Lab"]) + assert user.can_create_vm(vm_lab) + assert not user.can_create_vm(vm_prod) + assert not user.can_create_vm(vm_lab_admin) + assert not user.can_create_vm(non_vm) + assert user.can_create_vm(vioc_lab) + assert user.can_create_vm(vioc_prod) + assert not user.can_create_vm(vioc_lab_admin) + assert not user.can_create_vm(non_vm_ioc) + # User has only access to the lab networks and can only create a VM and VIOC in the lab + user = user_factory(groups=["foo", "CSEntry Lab"]) + assert user.can_create_vm(vm_lab) + assert not user.can_create_vm(vm_prod) + assert not user.can_create_vm(vm_lab_admin) + assert not user.can_create_vm(non_vm) + assert user.can_create_vm(vioc_lab) + assert not user.can_create_vm(vioc_prod) + assert not user.can_create_vm(vioc_lab_admin) + assert not user.can_create_vm(non_vm_ioc) + # User can't create any VM or VIOC + user = user_factory(groups=["one", "two"]) + assert not user.can_create_vm(vm_lab) + assert not user.can_create_vm(vm_prod) + assert not user.can_create_vm(vm_lab_admin) + assert not user.can_create_vm(non_vm) + assert not user.can_create_vm(vioc_lab) + assert not user.can_create_vm(vioc_prod) + assert not user.can_create_vm(vioc_lab_admin) + assert not user.can_create_vm(non_vm_ioc) + # Admin can create VM and VIOC + user = user_factory(groups=["CSEntry Admin"]) + assert user.can_create_vm(vm_lab) + assert user.can_create_vm(vm_prod) + assert user.can_create_vm(vm_lab_admin) + assert not user.can_create_vm(non_vm) + assert user.can_create_vm(vioc_lab) + assert user.can_create_vm(vioc_prod) + assert user.can_create_vm(vioc_lab_admin) + assert not user.can_create_vm(non_vm_ioc) + + def test_network_ip_properties(network_factory): # Create some networks network1 = network_factory( diff --git a/tests/functional/test_web.py b/tests/functional/test_web.py index f37dfe4305957cb184f1f1d85cf6107ef5d701bf..a5030fc89badec875302d15a8077ae70edae04f9 100644 --- a/tests/functional/test_web.py +++ b/tests/functional/test_web.py @@ -429,3 +429,71 @@ def test_create_interface( assert interface.mac == mac assert interface.name == name assert interface.host == host + + +def check_vm_creation_response(response, success=True): + assert response.status_code == 200 + assert (b"View task" in response.data) is success + assert (b"View host" in response.data) is not success + assert (b"Please contact an admin user" in response.data) is not success + + +def test_create_vm( + client, + domain_factory, + network_factory, + device_type_factory, + host_factory, + interface_factory, +): + virtualmachine = device_type_factory(name="VirtualMachine") + domain_prod = domain_factory(name="prod.example.org") + domain_lab = domain_factory(name="lab.example.org") + network_prod = network_factory(domain=domain_prod) + network_lab = network_factory(domain=domain_lab) + vm_prod = host_factory(device_type=virtualmachine) + interface_factory(name=vm_prod.name, host=vm_prod, network=network_prod) + vioc_prod = host_factory(device_type=virtualmachine, is_ioc=True) + interface_factory(name=vioc_prod.name, host=vioc_prod, network=network_prod) + vm_lab = host_factory(device_type=virtualmachine) + interface_factory(name=vm_lab.name, host=vm_lab, network=network_lab) + vioc_lab = host_factory(device_type=virtualmachine, is_ioc=True) + interface_factory(name=vioc_lab.name, host=vioc_lab, network=network_lab) + form = {"cores": 1, "memory": 4, "disk": 15, "osversion": "centos7"} + # User has access to the lab networks and can create VM and VIOC there + login(client, "user_lab", "userlab") + response = client.post( + f"/network/hosts/view/{vm_prod.name}", data=form, follow_redirects=True + ) + check_vm_creation_response(response, success=False) + response = client.post( + f"/network/hosts/view/{vioc_prod.name}", data=form, follow_redirects=True + ) + check_vm_creation_response(response, success=False) + response = client.post( + f"/network/hosts/view/{vm_lab.name}", data=form, follow_redirects=True + ) + check_vm_creation_response(response, success=True) + response = client.post( + f"/network/hosts/view/{vioc_lab.name}", data=form, follow_redirects=True + ) + check_vm_creation_response(response, success=True) + logout(client) + # User has access to the prod networks but can only create VIOC due to ALLOWED_VM_CREATION_DOMAINS + login(client, "user_prod", "userprod") + response = client.post( + f"/network/hosts/view/{vm_prod.name}", data=form, follow_redirects=True + ) + check_vm_creation_response(response, success=False) + response = client.post( + f"/network/hosts/view/{vioc_prod.name}", data=form, follow_redirects=True + ) + check_vm_creation_response(response, success=True) + response = client.post( + f"/network/hosts/view/{vm_lab.name}", data=form, follow_redirects=True + ) + check_vm_creation_response(response, success=False) + response = client.post( + f"/network/hosts/view/{vioc_lab.name}", data=form, follow_redirects=True + ) + check_vm_creation_response(response, success=False)