from abc import ABCMeta, abstractmethod import ldap import os import random from base64 import b64encode from database import BookingDataBase class VPN_BaseClass: """ the vpn handler abstract class / interface """ __metaclass__ = ABCMeta @abstractmethod def __init__(self, config): """ config is the parsed vpn.yaml file """ pass @abstractmethod def makeNewUser(self, user=None): """ This method is called when a vpn user is needed. This method should create a vpn user in whatever runs the vpn in our infrastructure. returns the credentials for the vpn user and some uid that will be associated with the booking in the database. This uid is used to track the vpn user and to delete the user when there are no bookings associated with that uid. """ user = "username" passwd = "password" uid = "some way for you to identify this user in the database" return user, passwd, uid @abstractmethod def removeOldUsers(self): """ checks the list of all vpn users against a list of vpn users associated with active bookings and removes users who dont have an active booking If you want your vpn accounts to be persistent, you can just ignore this """ pass names = [ 'frodo baggins', 'samwise gamgee', 'peregrin took', 'meriadoc brandybuck', 'bilbo baggins', 'gandalf grey', 'aragorn dunadan', 'arwen evenstar', 'saruman white', 'pippin took', 'merry brandybuck', 'legolas greenleaf', 'gimli gloin', 'anakin skywalker', 'padme amidala', 'han solo', 'jabba hut', 'mace windu', 'sount dooku', 'qui-gon jinn', 'admiral ackbar', 'emperor palpatine' ] class VPN: """ This class communicates with the ldap server to manage vpn users. This class extends the above ABC, and implements the makeNewUser, removeOldUser, and __init__ abstract functions you must override to extend the VPN_BaseClass """ def __init__(self, config): """ init takes the parsed vpn config file as an arguement. automatically connects and authenticates on the ldap server based on the configuration file """ self.config = config server = config['server'] self.uri = "ldap://"+server self.conn = None user = config['authentication']['user'] pswd = config['authentication']['pass'] if os.path.isfile(pswd): pswd = open(pswd).read() self.connect(user, pswd) def connect(self, root_dn, root_pass): """ Opens a connection to the server in the config file and authenticates as the given user """ self.conn = ldap.initialize(self.uri) self.conn.simple_bind_s(root_dn, root_pass) def addUser(self, full_name, passwd): """ Adds a user to the ldap server. Creates the new user with the classes and in the directory given in the config file. full_name should be two tokens seperated by a space. The first token will become the username private helper function for the makeNewUser() """ first = full_name.split(' ')[0] last = full_name.split(' ')[1] user_dir = self.config['directory']['user'] user_dir += ','+self.config['directory']['root'] dn = "uid=" + first + ',' + user_dir record = [ ('objectclass', ['top', 'inetOrgPerson']), ('uid', first), ('cn', full_name), ('sn', last), ('userpassword', passwd), ('ou', self.config['directory']['user'].split('=')[1]) ] self.conn.add_s(dn, record) return dn def makeNewUser(self, name=None): """ creates a new user in the ldap database, with the given name if supplied. If no name is given, we will try to select from the pre-written list above, and will resort to generating a random string as a username if the preconfigured names are all taken. Returns the username and password the user needs to authenticate, and the dn that we can use to manage the user. """ if name is None: i = 0 while not self.checkName(name): i += 1 if i == 20: name = self.randoString(8) name += ' '+self.randoString(8) break # generates a random name to prevent infinite loop name = self.genUserName() passwd = self.randoString(15) dn = self.addUser(name, passwd) return name, passwd, dn def checkName(self, name): """ returns true if the name is available """ if name is None: return False uid = name.split(' ')[0] base = self.config['directory']['user'] + ',' base += self.config['directory']['root'] filtr = '(uid=' + uid + ')' timeout = 5 ans = self.conn.search_st( base, ldap.SCOPE_SUBTREE, filtr, timeout=timeout ) return len(ans) < 1 @staticmethod def randoString(n): """ uses /dev/urandom to generate a random string of length n """ n = int(n) # defines valid characters alpha = 'abcdefghijklmnopqrstuvwxyz' alpha_num = alpha alpha_num += alpha.upper() alpha_num += "0123456789" # generates random string from /dev/urandom rnd = b64encode(os.urandom(3*n)).decode('utf-8') random_string = '' for char in rnd: if char in alpha_num: random_string += char return str(random_string[:n]) def genUserName(self): """ grabs a random name from the list above """ i = random.randint(0, len(names) - 1) return names[i] def deleteUser(self, dn): self.conn.delete(dn) def getAllUsers(self): """ returns all the user dn's in the ldap database in a list """ base = self.config['directory']['user'] + ',' base += self.config['directory']['root'] filtr = '(objectclass='+self.config['user']['objects'][-1]+')' timeout = 10 ans = self.conn.search_st( base, ldap.SCOPE_SUBTREE, filtr, timeout=timeout ) users = [] for user in ans: users.append(user[0]) # adds the dn of each user return users def removeOldUsers(self): """ removes users from the ldap server who dont have any active bookings. will not delete a user if their uid's are named in the config file as permanent users. """ db = self.config['database'] # the dn of all users who have an active booking active_users = BookingDataBase(db).getVPN() all_users = self.getAllUsers() for user in all_users: # checks if they are a permanent user if self.is_permanent_user(user): continue # deletes the user if they dont have an active booking if user not in active_users: self.deleteUser(user) def is_permanent_user(self, dn): for user in self.config['permanent_users']: if (user in dn) or (dn in user): return True return False VPN_BaseClass.register(VPN)