From 92fe8b221f62f4d7fcf9d261c29a33734c0734fb Mon Sep 17 00:00:00 2001 From: Nikolai R Kristiansen Date: Sun, 22 Feb 2015 17:17:56 +0100 Subject: [PATCH] AddNewUser ready for testing --- README | 37 ++++++++++++++------------------ neuf_kerberos/utils.py | 26 ++++++++++++++++++----- neuf_ldap/models.py | 46 +++++++++++++++++++++++++++++++++++++++- neuf_ldap/utils.py | 21 +++++++++++------- neuf_radius/utils.py | 3 ++- neuf_userinfo/ssh.py | 41 +++++++++++++++++++++++++++++++++++ neuf_userinfo/utils.py | 7 +----- neuf_userinfo/views.py | 8 +++---- requirements.txt | 1 + test/testdata.ldif | 12 +++++++++++ userinfosite/settings.py | 5 +++-- 11 files changed, 159 insertions(+), 48 deletions(-) create mode 100644 neuf_userinfo/ssh.py diff --git a/README b/README index 0225d33..83c50be 100644 --- a/README +++ b/README @@ -9,14 +9,14 @@ python manage.py runserver ## LDAP - sudo docker run -e LDAP_DOMAIN=neuf.no -e LDAP_ORGANISATION="Neuf" -e LDAP_ROOTPASS="toor" -p 389:389 -d osixia/openldap - ldapadd -D "cn=admin,dc=neuf,dc=no" -w "toor" -f test/testdata.ldif + sudo docker run -e LDAP_DOMAIN=neuf.no -e LDAP_ORGANISATION="Neuf" -e LDAP_ADMIN_PWD="toor" -p 389:389 -d nikolaik/docker-openldap + ldapadd -D "cn=admin,dc=neuf,dc=no" -w "toor" -f test/testdata.ldif # Testdata # Verify import ldapsearch -x -b dc=neuf,dc=no # Login with test@example.com:test ## Inside - # create the Inside database tables, if they don't exist already + # create the Inside database tables, if they don't exist already: python manage.py syncdb --database=inside For simple dev you need at least: @@ -25,30 +25,25 @@ For simple dev you need at least: * 1 UserGroupRelationShip (din_usergrouprelationship) ## RADIUS - # create the radius database tables, if they don't exist already + # create the radius database tables, if they don't exist already: python manage.py syncdb --database=radius - -# External dependencies: - - LDAP: Read the tutorial on [Howto setup a LDAP server with Docker](https://edb.neuf.no/wiki/index.php?title=Howto_setup_a_LDAP_server_with_Docker) - - Kerberos: TODO - - RADIUS: TODO - - Inside: Never going away! See https://git.neuf.no/edb/inside#tab-readme for code and installation - +## Homedirs + - settings.FILESERVER_HOST:~/.ssh/authorized_keys must contain the public key of settings.FILESERVER_USER + - settings.FILESERVER_USER might need some sudo access # TODO - - Rewrite usersync.neuf.no as one or more endpoints - - Create new user in LDAP (username, first_name, last_name, email) - - add to groups (usergroup, dns-alle), it not exists, create - - Set LDAP password - - Create automount entry - - Create kerberos principal and set password - - Create homedir on wii - - Set RADIUS password + - Rewrite usersync.neuf.no as one or more endpoints [DONE?] + - On password update, then update ldap hash on InsideUser.ldap_password + - allow non LDAP-users (conditional LDAP-stuff), because we don't want LDAP filled with non-active members - Integrity check view which provides two way diffs for the following: - user vs user group - user vs kerberos principal - user vs auto mount entry - Add email addr alias (usernam@s.no, firstname.lastname@s.n) - - On password update, then update ldap hash on InsideUser.ldap_password - - allow non LDAP-users (conditional LDAP-stuff), because we don't want LDAP filled with non-active members \ No newline at end of file + +# External dependencies: + - LDAP: Read the tutorial on [Howto setup a LDAP server with Docker](https://edb.neuf.no/wiki/index.php?title=Howto_setup_a_LDAP_server_with_Docker) + - Kerberos: TODO + - RADIUS: Standard MySQL. + - Inside: Never going away! See https://git.neuf.no/edb/inside#tab-readme for code and installation diff --git a/neuf_kerberos/utils.py b/neuf_kerberos/utils.py index ee4a271..9510477 100644 --- a/neuf_kerberos/utils.py +++ b/neuf_kerberos/utils.py @@ -31,13 +31,13 @@ def get_kerberos_principal(username): def set_kerberos_password(username, raw_password): - principal = "{0}@{1}".format(username, settings.KERBEROS_REALM) - kadmin_query = " -p {0} -w {1} -q 'change_password -pw {2} {3}'".format( + principal = "{}@{}".format(username, settings.KERBEROS_REALM) + kadmin_query = " -p {} -w {} -q 'change_password -pw {} {}'".format( settings.KERBEROS_PASSWORD_CHANGING_PRINCIPAL, settings.KERBEROS_PASSWORD, raw_password, principal) - # FIXME: Running this shell command needs escaping: http://qntm.org/bash + # FIXME: Running this shell command could need escaping: http://qntm.org/bash p = Popen('kadmin' + kadmin_query, shell=True, stdout=PIPE, stderr=PIPE) output, error = p.communicate() if error: @@ -56,5 +56,21 @@ def format_krb5_date(date): def kerberos_create_principal(user): - # TODO - pass \ No newline at end of file + principal = "{}@{}".format(user['username'], settings.KERBEROS_REALM) + kadmin_query = " -p '{}' -w {} -q 'add_principal {} -pw {}'".format( + settings.KERBEROS_PASSWORD_CHANGING_PRINCIPAL, + settings.KERBEROS_PASSWORD, + principal, + user['password']) + p = Popen('kadmin' + kadmin_query, shell=True, stdout=PIPE, stderr=PIPE) + output, error = p.communicate() + if error: + # KADM5_AUTH_ADD (requires "add" privilege) + # KADM5_BAD_MASK (shouldn't happen) + # KADM5_DUP (principal exists already) + # KADM5_UNK_POLICY (policy does not exist) + # KADM5_PASS_Q_* (password quality violations) + logger.error(error) + return False + + return True \ No newline at end of file diff --git a/neuf_ldap/models.py b/neuf_ldap/models.py index e6b0b0f..64b1635 100644 --- a/neuf_ldap/models.py +++ b/neuf_ldap/models.py @@ -91,4 +91,48 @@ class LdapGroup(ldapdb.models.Model): def __unicode__(self): return self.name -# TODO Automount: http://www.openldap.org/faq/data/cache/599.html \ No newline at end of file + +class LdapAutomountMap(ldapdb.models.Model): + """ + Represents an LDAP automountMap + Ref: http://www.openldap.org/faq/data/cache/599.html + """ + + connection_name = 'ldap' + + # LDAP meta-data + base_dn = settings.LDAP_AUTOMOUNT_DN + object_classes = ['automountMap'] + + ou = CharField(db_column='ou', primary_key=True) # F.ex auto.home + + def __unicode__(self): + return self.ou + + +class LdapAutomountHome(ldapdb.models.Model): + """ + Represents an LDAP automount with hardcoded 'auto.home' in the base_dn (!) + Ref: http://www.openldap.org/faq/data/cache/599.html + Ref: http://lists.bolloretelecom.eu/pipermail/django-ldapdb/2012-April/000113.html + """ + + connection_name = 'ldap' + + # LDAP meta-data + base_dn = 'ou=auto.home,{}'.format(settings.LDAP_AUTOMOUNT_DN) + object_classes = ['automount'] + + username = CharField(db_column='cn', primary_key=True) + automountInformation = CharField(db_column='automountInformation') + + def set_automount_info(self, username=None): + krb5_automount_info = "-fstype=nfs4,rw,sec=krb5 {}:{}/{}".format( + settings.FILESERVER_HOST, + settings.FILESERVER_HOME_PATH, + username if username is not None else self.username) + + self.automountInformation = krb5_automount_info + + def __unicode__(self): + return self.username \ No newline at end of file diff --git a/neuf_ldap/utils.py b/neuf_ldap/utils.py index 975b2a4..2f62dde 100644 --- a/neuf_ldap/utils.py +++ b/neuf_ldap/utils.py @@ -1,11 +1,9 @@ # coding: utf-8 from __future__ import unicode_literals -import json from django.conf import settings - +import logging import os from passlib.hash import ldap_salted_sha1 -import logging logger = logging.getLogger(__name__) @@ -63,12 +61,14 @@ def ldap_create_user(user): user_groups = LdapGroup.objects.filter(gid__gte=settings.LDAP_USER_GID_MIN, gid__lte=settings.LDAP_USER_GID_MAX) user_groups = user_groups.order_by('-gid').values_list('gid', flat=True) + if len(user_groups) == 0: + return settings.LDAP_USER_GID_MIN + if len(user_groups) > 0 and user_groups[0] >= settings.LDAP_USER_GID_MAX: logger.exception("UID larger than LDAP_USER_GID_MAX") return user_groups[0] + 1 - groups = user.pop('groups') # User full_name = '{} {}'.format(user['firstname'], user['lastname']) user_data = { @@ -92,15 +92,20 @@ def ldap_create_user(user): ldap_user_group.save() # Add groups - ldap_groups = LdapGroup.objects.filter(name__in=groups) + ldap_groups = LdapGroup.objects.filter(name__in=user['groups']) for g in ldap_groups: if user['username'] not in g.members: g.members.append(user['username']) g.save() # Finito! + return True -def ldap_create_automount(user): - # TODO - pass \ No newline at end of file +def ldap_create_automount(username): + from neuf_ldap.models import LdapAutomountHome + automount = LdapAutomountHome(username=username) + automount.set_automount_info() + automount.save() + + return True diff --git a/neuf_radius/utils.py b/neuf_radius/utils.py index c2c68c3..b28169f 100644 --- a/neuf_radius/utils.py +++ b/neuf_radius/utils.py @@ -27,4 +27,5 @@ def set_radius_password(username, raw_password): def radius_create_user(username, password): - set_radius_password(username, password) \ No newline at end of file + set_radius_password(username, password) + return True \ No newline at end of file diff --git a/neuf_userinfo/ssh.py b/neuf_userinfo/ssh.py new file mode 100644 index 0000000..1f491f2 --- /dev/null +++ b/neuf_userinfo/ssh.py @@ -0,0 +1,41 @@ +from django.conf import settings +from fabric.api import settings as fab_settings +from fabric.operations import run +import logging +import os + +logger = logging.getLogger(__name__) + + +def create_home_dir(username): + """ + Create homedir for username + - Copy /etc/skel + - make user + This use fabric (SSH) for running remote commands + + Ref: + - http://stackoverflow.com/questions/4888568/can-i-catch-error-codes-when-using-fabric-to-run-calls-in-a-remote-shell + - http://stackoverflow.com/questions/6741523/using-python-fabric-without-the-command-line-tool-fab + """ + host = settings.FILESERVER_HOST + path = os.path.join(settings.FILESERVER_HOME_PATH, username) + host_string = '{}@{}'.format(settings.FILESERVER_USER, host) + + with fab_settings(host_string=host_string, warn_only=True): + user_does_not_exist = run('id -u {}'.format(username)) + if user_does_not_exist: + logger.error("User '{}' does not exist, user must exist in LDAP before creating home dir.".format(username)) + return False + + # FIXME: just sudo with one script sudo('some_command', shell=False) instead? + path_exists = run('[ -d "{}" ]; echo $?'.format(path)) + if path_exists: + logger.error("Path '{}:{}' already exists, doing nothing.".format(host, path)) + return False + + run('cp -r /etc/skel {}'.format(path)) + run('chown -R {0}:{0} {1}'.format(username, path)) + run('chmod -R 700 {}'.format(path)) + + return True diff --git a/neuf_userinfo/utils.py b/neuf_userinfo/utils.py index 7e7e2d5..fa07394 100644 --- a/neuf_userinfo/utils.py +++ b/neuf_userinfo/utils.py @@ -48,9 +48,4 @@ def decrypt_rijndael(key, encoded): padded_text += r.decrypt(ciphertext[start:start+BLOCK_SIZE]) plaintext = padded_text.split('\x00', 1)[0] - return plaintext - - -def create_homedir(username): - # TODO - pass \ No newline at end of file + return plaintext \ No newline at end of file diff --git a/neuf_userinfo/views.py b/neuf_userinfo/views.py index 1b1efb5..39e075c 100644 --- a/neuf_userinfo/views.py +++ b/neuf_userinfo/views.py @@ -21,7 +21,7 @@ from neuf_ldap.models import LdapGroup, LdapUser from neuf_ldap.utils import ldap_create_user, ldap_create_automount from neuf_radius.utils import radius_create_user from neuf_userinfo.forms import NewUserForm -from neuf_userinfo.utils import create_homedir +from neuf_userinfo.ssh import create_home_dir def index(request): @@ -153,12 +153,12 @@ class AddNewUserView(View): results = { 'user': user, 'ldap_user': ldap_create_user(user), - 'ldap_automount': ldap_create_automount(user), + 'homedir': create_home_dir(user['username']), + 'ldap_automount': ldap_create_automount(user['username']), 'kerberos_principal': kerberos_create_principal(user), - 'homedir': create_homedir(user['username']), # FIXME run ssh script? 'radius': radius_create_user(user['username'], user['password']) } - # import pprint; pprint.pprint(results) + import pprint; pprint.pprint(results) return JsonResponse({'results': 'success'}) diff --git a/requirements.txt b/requirements.txt index 4b5e10b..4334d8b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ django-auth-ldap==1.2.0 django-extensions==1.5.0 django-ldapdb==0.3.2 dj-static==0.0.6 +Fabric==1.10.1 mysqlclient==1.3.5 passlib==1.6.2 python-ldap==2.4.15 diff --git a/test/testdata.ldif b/test/testdata.ldif index eefe4f1..8f21ce3 100644 --- a/test/testdata.ldif +++ b/test/testdata.ldif @@ -61,3 +61,15 @@ cn: edbadmin gidNumber: 3 description: Group account memberUid: test + +# Root dn for all automounts +dn: ou=Automount,dc=neuf,dc=no +objectClass: top +objectClass: organizationalUnit +ou: Automount + +# Automount map for auto.home +dn: ou=auto.home,ou=Automount,dc=neuf,dc=no +objectClass: top +objectClass: automountMap +ou: auto.home diff --git a/userinfosite/settings.py b/userinfosite/settings.py index 0ea000f..d22781c 100644 --- a/userinfosite/settings.py +++ b/userinfosite/settings.py @@ -213,8 +213,9 @@ INSIDE_USERSYNC_API_KEY = '' INSIDE_USERSYNC_ENC_KEY = '' # Home dir -FILESERVER_HOST = "wii.neuf.no" -FILESERVER_HOME_PATH = "/fileserver/homes" +FILESERVER_HOST = "localhost" +FILESERVER_USER = 'nikolark' # change this to your own user for development +FILESERVER_HOME_PATH = "/tmp/homes" try: from local_settings import *