1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2024-12-24 23:25:24 +00:00

Merge pull request #506 from Info-IIG/develop

New additions
This commit is contained in:
catborise 2022-06-20 08:54:10 +03:00 committed by GitHub
commit 2c07df4c8f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 153 additions and 24 deletions

View file

@ -397,11 +397,21 @@ sudo sed -i "s/LDAP_URL = ''/LDAP_URL = 'myldap.server.com'/g"" /srv/webvirtclou
sudo sed -i "s/LDAP_ROOT_DN = ''/LDAP_ROOT_DN = 'dc=server,dc=com'/g"" /srv/webvirtcloud/webvirtcloud/settings.py sudo sed -i "s/LDAP_ROOT_DN = ''/LDAP_ROOT_DN = 'dc=server,dc=com'/g"" /srv/webvirtcloud/webvirtcloud/settings.py
``` ```
Set the user that has browse access to LDAP and its password Set the passphrase to decrypt the password
```bash
sudo sed -i "s/pass:MYPASSPHRASE/pass:MYTRUEPASSPHRASE/g" /srv/webvirtcloud/webvirtcloud/.dec_ldap_pwd.sh
```
Encrypt the password
```bash
echo MYPASSWORD | openssl enc -pbkdf2 -salt -pass pass:MYTRUEPASSPHRASE | base64
```
Set the user that has browse access to LDAP and its password encrypted
```bash ```bash
sudo sed -i "s/LDAP_MASTER_DN = ''/LDAP_MASTER_DN = 'cn=admin,ou=users,dc=kendar,dc=org'/g"" /srv/webvirtcloud/webvirtcloud/settings.py sudo sed -i "s/LDAP_MASTER_DN = ''/LDAP_MASTER_DN = 'cn=admin,ou=users,dc=kendar,dc=org'/g"" /srv/webvirtcloud/webvirtcloud/settings.py
sudo sed -i "s/LDAP_MASTER_PW = ''/LDAP_MASTER_PW = 'password'/g"" /srv/webvirtcloud/webvirtcloud/settings.py sudo sed -i "s/LDAP_MASTER_PW_ENC = ''/LDAP_MASTER_PW_ENC = 'MYPASSWORDENCRYPTED'/g"" /srv/webvirtcloud/webvirtcloud/settings.py
``` ```
Set the attribute that will be used to find the username, i usually use the cn Set the attribute that will be used to find the username, i usually use the cn

View file

@ -0,0 +1,15 @@
from django.db import models, migrations
def apply_migration(apps, schema_editor):
Group = apps.get_model('auth', 'Group')
Group.objects.create(name='Technicians')
class Migration(migrations.Migration):
dependencies = [
('admin', '0002_auto_20200609_0830'),
]
operations = [
migrations.RunPython(apply_migration)
]

View file

@ -41,7 +41,7 @@ def console(request):
host = int(temptoken[0]) host = int(temptoken[0])
uuid = temptoken[1] uuid = temptoken[1]
if not request.user.is_superuser: if not request.user.is_superuser and not request.user.has_perm("instances.view_instances"):
try: try:
userInstance = UserInstance.objects.get( userInstance = UserInstance.objects.get(
instance__compute_id=host, instance__uuid=uuid, user__id=request.user.id instance__compute_id=host, instance__uuid=uuid, user__id=request.user.id

View file

@ -28,6 +28,7 @@ class Instance(models.Model):
uuid = models.CharField(_('uuid'), max_length=36, db_index=True) uuid = models.CharField(_('uuid'), max_length=36, db_index=True)
is_template = models.BooleanField(_('is template'), default=False) is_template = models.BooleanField(_('is template'), default=False)
created = models.DateTimeField(_('created'), auto_now_add=True) created = models.DateTimeField(_('created'), auto_now_add=True)
drbd = models.CharField(_('drbd'), max_length=24, default="None")
objects = InstanceManager() objects = InstanceManager()
@ -214,6 +215,8 @@ class PermissionSet(models.Model):
permissions = [ permissions = [
('clone_instances', 'Can clone instances'), ('clone_instances', 'Can clone instances'),
('passwordless_console', _('Can access console without password')), ('passwordless_console', _('Can access console without password')),
('view_instances', 'Can view instances'),
('snapshot_instances', 'Can snapshot instances'),
] ]
managed = False managed = False

View file

@ -25,7 +25,7 @@
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<div class="col-lg-12"> <div class="col-lg-12">
{% if app_settings.VIEW_INSTANCES_LIST_STYLE == 'grouped' and request.user.is_superuser %} {% if app_settings.VIEW_INSTANCES_LIST_STYLE == 'grouped' and request.user.is_superuser or 'instances.view_instances' in perms %}
{% include 'allinstances_index_grouped.html' %} {% include 'allinstances_index_grouped.html' %}
{% else %} {% else %}
{% include 'allinstances_index_nongrouped.html' %} {% include 'allinstances_index_nongrouped.html' %}

View file

@ -7,6 +7,7 @@
<th scope="col">{% trans "Name" %}<br>{% trans "Description" %}</th> <th scope="col">{% trans "Name" %}<br>{% trans "Description" %}</th>
<th scope="col" class="d-none d-sm-table-cell">{% trans "User"%}</th> <th scope="col" class="d-none d-sm-table-cell">{% trans "User"%}</th>
<th scope="col">{% trans "Status" %}</th> <th scope="col">{% trans "Status" %}</th>
<th scope="col">{% trans "Role/Disk" %}</th>
<th scope="col" class="d-none d-sm-table-cell">{% trans "VCPU" %}</th> <th scope="col" class="d-none d-sm-table-cell">{% trans "VCPU" %}</th>
<th scope="col" class="d-none d-sm-table-cell">{% trans "Memory" %}</th> <th scope="col" class="d-none d-sm-table-cell">{% trans "Memory" %}</th>
<th scope="col" style="width:200px;" data-sortable="false">{% trans "Actions" %} & {% trans "Mem Usage" %}</th> <th scope="col" style="width:200px;" data-sortable="false">{% trans "Actions" %} & {% trans "Mem Usage" %}</th>
@ -27,6 +28,7 @@
<td> <td>
<span class="text-success">{% trans "Connected" %}</span> <span class="text-success">{% trans "Connected" %}</span>
</td> </td>
<td class="d-none d-sm-table-cell"></td>
<td class="d-none d-sm-table-cell text-center">{{ compute.cpu_count }}</td> <td class="d-none d-sm-table-cell text-center">{{ compute.cpu_count }}</td>
<td class="d-none d-sm-table-cell text-right">{{ compute.ram_size|filesizeformat }}</td> <td class="d-none d-sm-table-cell text-right">{{ compute.ram_size|filesizeformat }}</td>
<td> <td>
@ -62,6 +64,9 @@
<span class="text-warning">{% trans "Suspended" %}</span> <span class="text-warning">{% trans "Suspended" %}</span>
{% endif %} {% endif %}
</td> </td>
<td>
{% if instance.drbd == "Primary/OK" or instance.drbd == "Secondary/OK" %}<span class="text-success">{% else %}<span class="text-danger">{% endif %}{{ instance.drbd }}</span>
</td>
<td>{{ instance.proxy.instance.info.3 }}</td> <td>{{ instance.proxy.instance.info.3 }}</td>
<td>{{ instance.cur_memory }} MB</td> <td>{{ instance.cur_memory }} MB</td>
<td class="text-nowrap"> <td class="text-nowrap">

View file

@ -7,6 +7,7 @@
<th scope="col">{% trans 'Host' %}<br>{% trans 'User' %}</th> <th scope="col">{% trans 'Host' %}<br>{% trans 'User' %}</th>
{% endif %} {% endif %}
<th scope="col">{% trans 'Status' %}</th> <th scope="col">{% trans 'Status' %}</th>
<th scope="col">{% trans 'Role/Disk' %}</th>
<th scope="col">{% trans 'VCPU' %}</th> <th scope="col">{% trans 'VCPU' %}</th>
<th scope="col">{% trans 'Memory' %}</th> <th scope="col">{% trans 'Memory' %}</th>
<th scope="col" data-sortable="false">{% trans 'Actions' %}</th> <th scope="col" data-sortable="false">{% trans 'Actions' %}</th>
@ -44,6 +45,9 @@
{% if instance.proxy.instance.info.0 == 3 %}<span {% if instance.proxy.instance.info.0 == 3 %}<span
class="text-warning">{% trans "Suspended" %}</span>{% endif %} class="text-warning">{% trans "Suspended" %}</span>{% endif %}
</td> </td>
<td>
{% if instance.drbd == "Primary/OK" or instance.drbd == "Secondary/OK" %}<span class="text-success">{% else %}<span class="text-danger">{% endif %}{{ instance.drbd }}</span>
</td>
<td>{{ instance.proxy.instance.info.3 }}</td> <td>{{ instance.proxy.instance.info.3 }}</td>
<td>{{ instance.cur_memory }} MB</td> <td>{{ instance.cur_memory }} MB</td>
<td class="text-nowrap"> <td class="text-nowrap">

View file

@ -93,7 +93,7 @@
{% trans "Resize" %} {% trans "Resize" %}
</button> </button>
</li> </li>
{% if allow_admin_or_not_template %} {% if allow_admin_or_not_template and 'instances.snapshot_instances' in perms %}
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<button class="nav-link action-button" id="snapshots-tab" aria-controls="snapshots" data-bs-toggle="pill" data-bs-target="#snapshots" type="button" role="tab" aria-selected="false"> <button class="nav-link action-button" id="snapshots-tab" aria-controls="snapshots" data-bs-toggle="pill" data-bs-target="#snapshots" type="button" role="tab" aria-selected="false">
<span id="action-block" class="fa fa-camera" aria-hidden="true"></span> <span id="action-block" class="fa fa-camera" aria-hidden="true"></span>

View file

@ -4,6 +4,7 @@ import os
import re import re
import socket import socket
import time import time
import subprocess
from bisect import insort from bisect import insort
from accounts.models import UserInstance, UserSSHKey from accounts.models import UserInstance, UserSSHKey
@ -46,7 +47,7 @@ def index(request):
for compute in computes: for compute in computes:
utils.refr(compute) utils.refr(compute)
if request.user.is_superuser: if request.user.is_superuser or request.user.has_perm("instances.view_instances"):
instances = Instance.objects.all().prefetch_related("userinstance_set") instances = Instance.objects.all().prefetch_related("userinstance_set")
else: else:
instances = Instance.objects.filter(userinstance__user=request.user).prefetch_related("userinstance_set") instances = Instance.objects.filter(userinstance__user=request.user).prefetch_related("userinstance_set")
@ -127,6 +128,9 @@ def instance(request, pk):
storages_host = sorted(instance.proxy.get_storages(True)) storages_host = sorted(instance.proxy.get_storages(True))
net_models_host = instance.proxy.get_network_models() net_models_host = instance.proxy.get_network_models()
instance.drbd = drbd_status(request, pk)
instance.save()
return render(request, "instance.html", locals()) return render(request, "instance.html", locals())
@ -134,6 +138,45 @@ def status(request, pk):
instance = get_instance(request.user, pk) instance = get_instance(request.user, pk)
return JsonResponse({"status": instance.proxy.get_status()}) return JsonResponse({"status": instance.proxy.get_status()})
def drbd_status(request, pk):
instance = get_instance(request.user, pk)
result = "None DRBD"
if instance.compute.type == 2:
conn = instance.compute.login + "@" + instance.compute.hostname
remoteDrbdStatus = subprocess.run(["ssh", conn, "sudo", "drbdadm", "status", "&&", "exit"], stdout=subprocess.PIPE, text=True)
if remoteDrbdStatus.stdout:
try:
instanceFindDrbd = re.compile(instance.name + '[_]*[A-Z]* role:(.+?)\n disk:(.+?)\n', re.IGNORECASE)
instanceDrbd = instanceFindDrbd.findall(remoteDrbdStatus.stdout)
primaryCount = 0
secondaryCount = 0
statusDisk = "OK"
for disk in instanceDrbd:
if disk[0] == "Primary":
primaryCount = primaryCount + 1
elif disk[0] == "Secondary":
secondaryCount = secondaryCount + 1
if disk[1] != "UpToDate":
statusDisk = "NOK"
if primaryCount > 0 and secondaryCount > 0:
statusRole = "NOK"
else:
if primaryCount > secondaryCount:
statusRole = "Primary"
else:
statusRole = "Secondary"
result = statusRole + "/" + statusDisk
except:
print("Error to get drbd role and status")
return result
def stats(request, pk): def stats(request, pk):
instance = get_instance(request.user, pk) instance = get_instance(request.user, pk)
@ -237,7 +280,7 @@ def get_instance(user, pk):
instance = get_object_or_404(Instance, pk=pk) instance = get_object_or_404(Instance, pk=pk)
user_instances = user.userinstance_set.all().values_list("instance", flat=True) user_instances = user.userinstance_set.all().values_list("instance", flat=True)
if user.is_superuser or instance.id in user_instances: if user.is_superuser or user.has_perm("instances.view_instances") or instance.id in user_instances:
return instance return instance
else: else:
raise Http404() raise Http404()
@ -770,7 +813,7 @@ def snapshot(request, pk):
instance = get_instance(request.user, pk) instance = get_instance(request.user, pk)
allow_admin_or_not_template = request.user.is_superuser or request.user.is_staff or not instance.is_template allow_admin_or_not_template = request.user.is_superuser or request.user.is_staff or not instance.is_template
if allow_admin_or_not_template: if allow_admin_or_not_template and request.user.has_perm("instances.snapshot_instances"):
name = request.POST.get("name", "") name = request.POST.get("name", "")
instance.proxy.create_snapshot(name) instance.proxy.create_snapshot(name)
msg = _("Create snapshot: %(snap)s") % {"snap": name} msg = _("Create snapshot: %(snap)s") % {"snap": name}
@ -781,7 +824,7 @@ def snapshot(request, pk):
def delete_snapshot(request, pk): def delete_snapshot(request, pk):
instance = get_instance(request.user, pk) instance = get_instance(request.user, pk)
allow_admin_or_not_template = request.user.is_superuser or request.user.is_staff or not instance.is_template allow_admin_or_not_template = request.user.is_superuser or request.user.is_staff or not instance.is_template
if allow_admin_or_not_template: if allow_admin_or_not_template and request.user.has_perm("instances.snapshot_instances"):
snap_name = request.POST.get("name", "") snap_name = request.POST.get("name", "")
instance.proxy.snapshot_delete(snap_name) instance.proxy.snapshot_delete(snap_name)
msg = _("Delete snapshot: %(snap)s") % {"snap": snap_name} msg = _("Delete snapshot: %(snap)s") % {"snap": snap_name}
@ -792,7 +835,7 @@ def delete_snapshot(request, pk):
def revert_snapshot(request, pk): def revert_snapshot(request, pk):
instance = get_instance(request.user, pk) instance = get_instance(request.user, pk)
allow_admin_or_not_template = request.user.is_superuser or request.user.is_staff or not instance.is_template allow_admin_or_not_template = request.user.is_superuser or request.user.is_staff or not instance.is_template
if allow_admin_or_not_template: if allow_admin_or_not_template and request.user.has_perm("instances.snapshot_instances"):
snap_name = request.POST.get("name", "") snap_name = request.POST.get("name", "")
instance.proxy.snapshot_revert(snap_name) instance.proxy.snapshot_revert(snap_name)
msg = _("Successful revert snapshot: ") msg = _("Successful revert snapshot: ")

18
webvirtcloud/.dec_ldap_pwd.sh Executable file
View file

@ -0,0 +1,18 @@
#!/bin/bash
#####
#
# LDAP PASSWORD DECRYPTION SCRIPT
#
#
#####
ENC_PASSWD=$1
echo $(echo $ENC_PASSWD | base64 -d | openssl enc -pbkdf2 -salt -d -pass pass:MYPASSPHRASE )

View file

@ -1,5 +1,5 @@
from django.contrib.auth.backends import ModelBackend from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User from django.contrib.auth.models import User, Group
from django.conf import settings from django.conf import settings
from accounts.models import UserAttributes, UserInstance, UserSSHKey from accounts.models import UserAttributes, UserInstance, UserSSHKey
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
@ -30,8 +30,11 @@ try:
return None return None
specificUser = connection.response[0] specificUser = connection.response[0]
userDn = str(specificUser.get('raw_dn'),'utf-8') userDn = str(specificUser.get('raw_dn'),'utf-8')
userGivenName = connection.entries[0].givenName
userSn = connection.entries[0].sn
userMail = connection.entries[0].mail
with Connection(server, userDn, password) as con: with Connection(server, userDn, password) as con:
return username return username, userGivenName, userSn, userMail
except Exception as e: except Exception as e:
print("LDAP Exception: {}".format(e)) print("LDAP Exception: {}".format(e))
return None return None
@ -44,30 +47,54 @@ try:
# Get the user information from the LDAP if he can be authenticated # Get the user information from the LDAP if he can be authenticated
isAdmin = False isAdmin = False
isStaff = False isStaff = False
isTechnician = False
if self.get_LDAP_user(username, password, settings.LDAP_SEARCH_GROUP_FILTER_ADMINS) is None: requeteLdap = self.get_LDAP_user(username, password, settings.LDAP_SEARCH_GROUP_FILTER_ADMINS)
if self.get_LDAP_user(username, password, settings.LDAP_SEARCH_GROUP_FILTER_STAFF) is None: if requeteLdap is None:
if self.get_LDAP_user(username, password, settings.LDAP_SEARCH_GROUP_FILTER_USERS) is None: requeteLdap = self.get_LDAP_user(username, password, settings.LDAP_SEARCH_GROUP_FILTER_STAFF)
print("User does not belong to any search group. Check LDAP_SEARCH_GROUP_FILTER in settings.") if requeteLdap is None:
return None requeteLdap = self.get_LDAP_user(username, password, settings.LDAP_SEARCH_GROUP_FILTER_TECHNICIANS)
else: if requeteLdap is None:
isStaff = True requeteLdap = self.get_LDAP_user(username, password, settings.LDAP_SEARCH_GROUP_FILTER_USERS)
if requeteLdap is None:
print("User does not belong to any search group. Check LDAP_SEARCH_GROUP_FILTER in settings.")
return None
else:
isTechnician = True
else:
isStaff = True
else: else:
isAdmin = True isAdmin = True
isStaff = True isStaff = True
techniciansGroup = Group.objects.get(name='Technicians')
try: try:
user = User.objects.get(username=username) user = User.objects.get(username=username)
attributes = UserAttributes.objects.get(user=user) attributes = UserAttributes.objects.get(user=user)
user.is_staff = isStaff
user.is_superuser = isAdmin
if isTechnician is False and user.groups.filter(name='Technicians').exists():
user.groups.remove(techniciansGroup)
elif isTechnician is True and user.groups.filter(name='Technicians').exists() is False:
user.groups.add(techniciansGroup)
else:
print("The user is already in the Technicians group")
user.save()
# TODO VERIFY # TODO VERIFY
except User.DoesNotExist: except User.DoesNotExist:
print("authenticate-create new user: {}".format(username)) print("authenticate-create new user: {}".format(username))
user = User(username=username) user = User(username=username)
user.first_name = requeteLdap[1]
user.last_name = requeteLdap[2]
user.email = requeteLdap[3]
user.is_active = True user.is_active = True
user.is_staff = isStaff user.is_staff = isStaff
user.is_superuser = isAdmin user.is_superuser = isAdmin
user.set_password(uuid.uuid4().hex) user.set_password(uuid.uuid4().hex)
user.save() user.save()
if isTechnician is True:
user.groups.add(techniciansGroup)
maxInstances = 1 maxInstances = 1
maxCpus = 1 maxCpus = 1
maxMemory = 128 maxMemory = 128

View file

@ -3,6 +3,7 @@ Django settings for webvirtcloud project.
""" """
import subprocess
from pathlib import Path from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
@ -263,16 +264,19 @@ LDAP_PORT = 389
USE_SSL = False USE_SSL = False
## The user with search rights on ldap. (e.g cn=admin,dc=kendar,dc=org) ## The user with search rights on ldap. (e.g cn=admin,dc=kendar,dc=org)
LDAP_MASTER_DN = '' LDAP_MASTER_DN = ''
LDAP_MASTER_PW = '' LDAP_MASTER_PW_ENC = ''
LDAP_MASTER_PW = subprocess.Popen(["bash", str(BASE_DIR) + "/webvirtcloud/.dec_ldap_pwd.sh", LDAP_MASTER_PW_ENC],stdout=subprocess.PIPE, text=True).stdout.read().strip('\n')
## The root dn (e.g. dc=kendar,dc=org) ## The root dn (e.g. dc=kendar,dc=org)
LDAP_ROOT_DN = '' LDAP_ROOT_DN = ''
## Queries to identify the users, i use groupOfUniqueNames on openldap ## Queries to identify the users, i use groupOfUniqueNames on openldap
### PLEASE BE SURE memberOf overlay is activated on slapd ### PLEASE BE SURE memberOf overlay is activated on slapd
## e.g. memberOf=cn=admins,cn=staff,cn=webvirtcloud,ou=groups,dc=kendar,dc=org ## e.g. memberOf=cn=admins,cn=staff,cn=technicians,cn=webvirtcloud,ou=groups,dc=kendar,dc=org
LDAP_SEARCH_GROUP_FILTER_ADMINS = '' LDAP_SEARCH_GROUP_FILTER_ADMINS = ''
## e.g. memberOf=cn=staff,cn=webvirtcloud,ou=groups,dc=kendar,dc=org ## e.g. memberOf=cn=staff,cn=technicians,cn=webvirtcloud,ou=groups,dc=kendar,dc=org
LDAP_SEARCH_GROUP_FILTER_STAFF = '' LDAP_SEARCH_GROUP_FILTER_STAFF = ''
## e.g. memberOf=cn=technicians,cn=webvirtcloud,ou=groups,dc=kendar,dc=org
LDAP_SEARCH_GROUP_FILTER_TECHNICIANS = ''
## e.g. memberOf=cn=webvirtcloud,ou=groups,dc=kendar,dc=org ## e.g. memberOf=cn=webvirtcloud,ou=groups,dc=kendar,dc=org
LDAP_SEARCH_GROUP_FILTER_USERS = '' LDAP_SEARCH_GROUP_FILTER_USERS = ''