diff --git a/README.md b/README.md index 7f9069f..cbbfb29 100644 --- a/README.md +++ b/README.md @@ -385,73 +385,55 @@ python manage.py test ## LDAP Configuration -The config options below can be changed in `webvirtcloud/settings.py` file. Variants for Active Directory and OpenLDAP are shown. This is a minimal config to get LDAP running, for further info read the [django-auth-ldap documentation](https://django-auth-ldap.readthedocs.io). +The example settings are based on an OpenLDAP server with groups defined as "cn" of class "groupOfUniqueNames" Enable LDAP ```bash -sudo sed -i "s~#\"django_auth_ldap.backend.LDAPBackend\",~\"django_auth_ldap.backend.LDAPBackend\",~g" /srv/webvirtcloud/webvirtcloud/settings.py +sudo sed -i "s/LDAP_ENABLED = False/LDAP_ENABLED = True/g"" /srv/webvirtcloud/webvirtcloud/settings.py ``` -Set the LDAP server name and bind DN +Set the LDAP server name and root DN -```python -# Active Directory -AUTH_LDAP_SERVER_URI = "ldap://example.com" -AUTH_LDAP_BIND_DN = "username@example.com" -AUTH_LDAP_BIND_PASSWORD = "password" - -# OpenLDAP -AUTH_LDAP_SERVER_URI = "ldap://example.com" -AUTH_LDAP_BIND_DN = "CN=username,CN=Users,OU=example,OU=com" -AUTH_LDAP_BIND_PASSWORD = "password" +```bash +sudo sed -i "s/LDAP_URL = ''/LDAP_URL = 'myldap.server.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 filter and user and group search base and filter - -```python -# Active Directory -AUTH_LDAP_USER_SEARCH = LDAPSearch( - "CN=Users,DC=example,DC=com", ldap.SCOPE_SUBTREE, "(sAMAccountName=%(user)s)" -) -AUTH_LDAP_GROUP_SEARCH = LDAPSearch( - "CN=Users,DC=example,DC=com", ldap.SCOPE_SUBTREE, "(objectClass=group)" -) -AUTH_LDAP_GROUP_TYPE = NestedActiveDirectoryGroupType() - -# OpenLDAP -AUTH_LDAP_USER_SEARCH = LDAPSearch( - "CN=Users,DC=example,DC=com", ldap.SCOPE_SUBTREE, "(cn=%(user)s)" -) -AUTH_LDAP_GROUP_SEARCH = LDAPSearch( - "CN=Users,DC=example,DC=com", ldap.SCOPE_SUBTREE, "(objectClass=groupOfUniqueNames)" -) -AUTH_LDAP_GROUP_TYPE = GroupOfUniqueNamesType() # import needs to be changed at the top of settings.py +Set the passphrase to decrypt the password +```bash +sudo sed -i "s/pass:MYPASSPHRASE/pass:MYTRUEPASSPHRASE/g" /srv/webvirtcloud/webvirtcloud/.dec_ldap_pwd.sh ``` -Set group which is required to access WebVirtCloud. You may set this to `False` to disable this filter. - -```python -AUTH_LDAP_REQUIRE_GROUP = "CN=WebVirtCloud Access,CN=Users,DC=example,DC=com" +Encrypt the password +```bash +echo MYPASSWORD | openssl enc -pbkdf2 -salt -pass pass:MYTRUEPASSPHRASE | base64 ``` -Populate user fields with values from LDAP +Set the user that has browse access to LDAP and its password encrypted -```python -AUTH_LDAP_USER_FLAGS_BY_GROUP = { - "is_staff": "CN=WebVirtCloud Staff,CN=Users,DC=example,DC=com", - "is_superuser": "CN=WebVirtCloud Admins,CN=Users,DC=example,DC=com", -} -AUTH_LDAP_USER_ATTR_MAP = { - "first_name": "givenName", - "last_name": "sn", - "email": "mail", -} +```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_PW_ENC = ''/LDAP_MASTER_PW_ENC = 'MYPASSWORDENCRYPTED'/g"" /srv/webvirtcloud/webvirtcloud/settings.py ``` -Now when you login with an LDAP user it will be assigned the rights defined. The user will be authenticated then with LDAP and authorized through the WebVirtCloud permissions. +Set the attribute that will be used to find the username, i usually use the cn -If you'd like to move a user from ldap to WebVirtCloud, just change its password from the UI and (eventually) remove from the group in LDAP. +```bash +sudo sed -i "s/LDAP_USER_UID_PREFIX = ''/LDAP_USER_UID_PREFIX = 'cn'/g"" /srv/webvirtcloud/webvirtcloud/settings.py +``` + +You can now create the filters to retrieve the users for the various group. This will be used during the user creation only + +```bash +sudo sed -i "s/LDAP_SEARCH_GROUP_FILTER_ADMINS = ''/LDAP_SEARCH_GROUP_FILTER_ADMINS = 'memberOf=cn=admins,dc=kendar,dc=org'/g"" /srv/webvirtcloud/webvirtcloud/settings.py +sudo sed -i "s/LDAP_SEARCH_GROUP_FILTER_STAFF = ''/LDAP_SEARCH_GROUP_FILTER_STAFF = 'memberOf=cn=staff,dc=kendar,dc=org'/g"" /srv/webvirtcloud/webvirtcloud/settings.py +sudo sed -i "s/LDAP_SEARCH_GROUP_FILTER_USERS = ''/LDAP_SEARCH_GROUP_FILTER_USERS = 'memberOf=cn=users,dc=kendar,dc=org'/g"" /srv/webvirtcloud/webvirtcloud/settings.py +``` + +Now when you login with an LDAP user it will be assigned the rights defined. The user will be authenticated then with ldap and authorized through the WebVirtCloud permissions. + +If you'd like to move a user from ldap to WebVirtCloud, just change its password from the UI and (eventually) remove from the group in ldap ## REST API / BETA diff --git a/conf/requirements.txt b/conf/requirements.txt index a9390be..64206b8 100644 --- a/conf/requirements.txt +++ b/conf/requirements.txt @@ -20,4 +20,3 @@ djangorestframework==3.14.0 drf-nested-routers==0.93.4 drf-yasg==1.21.7 markdown>=3.4.1 -django-auth-ldap==4.5.0 diff --git a/webvirtcloud/.dec_ldap_pwd.sh b/webvirtcloud/.dec_ldap_pwd.sh new file mode 100755 index 0000000..2cda920 --- /dev/null +++ b/webvirtcloud/.dec_ldap_pwd.sh @@ -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 ) + diff --git a/webvirtcloud/ldapbackend.py b/webvirtcloud/ldapbackend.py new file mode 100644 index 0000000..0f7a797 --- /dev/null +++ b/webvirtcloud/ldapbackend.py @@ -0,0 +1,142 @@ +from django.contrib.auth.backends import ModelBackend +from django.contrib.auth.models import User, Group +from django.conf import settings +from accounts.models import UserAttributes, UserInstance, UserSSHKey +from django.contrib.auth.models import Permission +from logs.models import Logs +import uuid + +try: + from ldap3 import Server, Connection, ALL + #/srv/webvirtcloud/ldap/ldapbackend.py + class LdapAuthenticationBackend(ModelBackend): + + def get_LDAP_user(self, username, password, filterString): + print('get_LDAP_user {}'.format(username)) + try: + server = Server(settings.LDAP_URL, port=settings.LDAP_PORT, + use_ssl=settings.USE_SSL,get_info=ALL) + connection = Connection(server, + settings.LDAP_MASTER_DN, + settings.LDAP_MASTER_PW, auto_bind=True) + connection.search(settings.LDAP_ROOT_DN, + '(&({attr}={login})({filter}))'.format( + attr=settings.LDAP_USER_UID_PREFIX, + login=username, + filter=filterString), attributes=['*']) + + if len(connection.response) == 0: + print('get_LDAP_user-no response') + return None + specificUser = connection.response[0] + 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: + return username, userGivenName, userSn, userMail + except Exception as e: + print("LDAP Exception: {}".format(e)) + return None + return None + + def authenticate(self, request, username=None, password=None, **kwargs): + if not settings.LDAP_ENABLED: + return None + print("authenticate_ldap") + # Get the user information from the LDAP if he can be authenticated + isAdmin = False + isStaff = False + isTechnician = False + + requeteLdap = self.get_LDAP_user(username, password, settings.LDAP_SEARCH_GROUP_FILTER_ADMINS) + isAdmin = requeteLdap is not None + isStaff = requeteLdap is not None + + if requeteLdap is None: + requeteLdap = self.get_LDAP_user(username, password, settings.LDAP_SEARCH_GROUP_FILTER_STAFF) + isStaff = requeteLdap is not None + + if requeteLdap is None: + requeteLdap = self.get_LDAP_user(username, password, settings.LDAP_SEARCH_GROUP_FILTER_TECHNICIANS) + isTechnician = requeteLdap is not None + + if requeteLdap is None: + 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 + + techniciansGroup = Group.objects.get(name='Technicians') + + try: + user = User.objects.get(username=username) + attributes = UserAttributes.objects.get(user=user) + user.is_staff = isStaff + user.is_superuser = isAdmin + if not isTechnician and user.groups.filter(name='Technicians').exists(): + user.groups.remove(techniciansGroup) + elif isTechnician and not user.groups.filter(name='Technicians').exists(): + user.groups.add(techniciansGroup) + else: + print("The user is already in the Technicians group") + user.save() + # TODO VERIFY + except User.DoesNotExist: + print(f"authenticate-create new user: {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_staff = isStaff + user.is_superuser = isAdmin + user.set_password(uuid.uuid4().hex) + user.save() + if isTechnician: + user.groups.add(techniciansGroup) + maxInstances = 1 + maxCpus = 1 + maxMemory = 128 + maxDiskSize = 1 + if isStaff: + maxMemory = 2048 + maxDiskSize = 20 + permission = Permission.objects.get(codename='clone_instances') + user.user_permissions.add(permission) + if isAdmin: + maxInstances = -1 + maxCpus = -1 + maxMemory = -1 + maxDiskSize = -1 + permission = Permission.objects.get(codename='clone_instances') + user.user_permissions.add(permission) + user.save() + UserAttributes.objects.create( + user=user, + max_instances=maxInstances, + max_cpus=maxCpus, + max_memory=maxMemory, + max_disk_size=maxDiskSize, + ) + user.save() + + print("authenticate-user created") + return user + + def get_user(self, user_id): + if not settings.LDAP_ENABLED: + return None + print("get_user_ldap") + try: + return User.objects.get(pk=user_id) + except User.DoesNotExist: + print("get_user-user not found") + return None +except: + class LdapAuthenticationBackend(ModelBackend): + def authenticate(self, request, username=None, password=None, **kwargs): + return None + def get_user(self, user_id): + return None diff --git a/webvirtcloud/settings.py.template b/webvirtcloud/settings.py.template index fabe94c..50dce23 100644 --- a/webvirtcloud/settings.py.template +++ b/webvirtcloud/settings.py.template @@ -3,9 +3,7 @@ Django settings for webvirtcloud project. """ -import ldap import subprocess -from django_auth_ldap.config import LDAPSearch, NestedActiveDirectoryGroupType from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. @@ -103,7 +101,7 @@ DATABASES = { AUTHENTICATION_BACKENDS = [ "django.contrib.auth.backends.ModelBackend", - #"django_auth_ldap.backend.LDAPBackend", + "webvirtcloud.ldapbackend.LdapAuthenticationBackend", ] LOGIN_URL = "/accounts/login/" @@ -282,23 +280,27 @@ EMAIL_HOST_PASSWORD = '' # LDAP Config # -AUTH_LDAP_SERVER_URI = "ldap://example.com" -AUTH_LDAP_BIND_DN = "username@example.com" -AUTH_LDAP_BIND_PASSWORD = "password" -AUTH_LDAP_USER_SEARCH = LDAPSearch( - "CN=Users,DC=example,DC=com", ldap.SCOPE_SUBTREE, "(sAMAccountName=%(user)s)" -) -AUTH_LDAP_GROUP_SEARCH = LDAPSearch( - "CN=Users,DC=example,DC=com", ldap.SCOPE_SUBTREE, "(objectClass=group)" -) -AUTH_LDAP_GROUP_TYPE = NestedActiveDirectoryGroupType() -AUTH_LDAP_REQUIRE_GROUP = "CN=WebVirtCloud Access,CN=Users,DC=example,DC=com" -AUTH_LDAP_USER_FLAGS_BY_GROUP = { - "is_staff": "CN=WebVirtCloud Staff,CN=Users,DC=example,DC=com", - "is_superuser": "CN=WebVirtCloud Admins,CN=Users,DC=example,DC=com", -} -AUTH_LDAP_USER_ATTR_MAP = { - "first_name": "givenName", - "last_name": "sn", - "email": "mail", -} +LDAP_ENABLED = False +LDAP_URL = '' +LDAP_PORT = 389 +USE_SSL = False +## The user with search rights on ldap. (e.g cn=admin,dc=kendar,dc=org) +LDAP_MASTER_DN = '' +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, encoding='utf8').stdout.read().strip('\n') +## The root dn (e.g. dc=kendar,dc=org) +LDAP_ROOT_DN = '' +## Queries to identify the users, i use groupOfUniqueNames on openldap + +### PLEASE BE SURE memberOf overlay is activated on slapd +## e.g. memberOf=cn=admins,cn=staff,cn=technicians,cn=webvirtcloud,ou=groups,dc=kendar,dc=org +LDAP_SEARCH_GROUP_FILTER_ADMINS = '' +## e.g. memberOf=cn=staff,cn=technicians,cn=webvirtcloud,ou=groups,dc=kendar,dc=org +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 +LDAP_SEARCH_GROUP_FILTER_USERS = '' + +## The user name prefix to identify the user name (e.g. cn) +LDAP_USER_UID_PREFIX = ''