#!/usr/bin/env python import grp import ldap import ldap.modlist import copy import threading from dm.common.utility.loggingManager import LoggingManager from dm.common.utility.configurationManager import ConfigurationManager from dm.common.exceptions.configurationError import ConfigurationError from dm.common.exceptions.internalError import InternalError from dm.common.exceptions.authenticationError import AuthenticationError from dm.common.exceptions.communicationError import CommunicationError from dmSubprocess import DmSubprocess class LdapLinuxPlatformUtility: SUDO_CMD = '/usr/bin/sudo' GROUPADD_CMD = '/usr/sbin/groupadd' USERMOD_CMD = '/usr/sbin/usermod' SETFACL_CMD = '/usr/bin/setfacl' CHOWN_CMD = '/bin/chown' GPASSWD_CMD = '/usr/bin/gpasswd' NSCD_CMD = '/usr/sbin/nscd' FIND_CMD = '/bin/find' CONFIG_SECTION_NAME = 'LdapLinuxPlatformUtility' REFRESH_AUTH_FILES_COMMAND_KEY = 'refreshauthfilescommand' TIMER_DELAY_IN_SECONDS = 10 def __init__(self, serverUrl, adminDn, adminPasswordFile, groupDnFormat, minGidNumber=None): self.serverUrl = serverUrl self.adminDn = adminDn self.groupDnFormat = groupDnFormat self.minGidNumber = minGidNumber self.refreshAuthFilesCommand = None self.refreshAuthFilesTimer = None self.getLogger().debug('Min GID number: %s' % minGidNumber) # Remove first entry from the dn format to get tree base self.groupBaseDn = ','.join(groupDnFormat.split(',')[1:]) self.getLogger().debug('Group base DN: %s' % self.groupBaseDn) self.getLogger().debug('Using LDAP Admin password file: %s' % adminPasswordFile) self.adminPassword = open(adminPasswordFile, 'r').readline().strip() if not self.adminPassword: raise ConfigurationError('LDAP password could not be found in %s file' % adminPasswordFile) self.ldapClient = None self.__configure() def __configure(self): cm = ConfigurationManager.getInstance() configItems = cm.getConfigItems(LdapLinuxPlatformUtility.CONFIG_SECTION_NAME) self.getLogger().debug('Got config items: %s' % configItems) self.refreshAuthFilesCommand = cm.getConfigOption(LdapLinuxPlatformUtility.CONFIG_SECTION_NAME, LdapLinuxPlatformUtility.REFRESH_AUTH_FILES_COMMAND_KEY) self.getLogger().debug('Refresh auth files command: %s' % self.refreshAuthFilesCommand) @classmethod def getLogger(cls): logger = LoggingManager.getInstance().getLogger(cls.__name__) return logger @classmethod def executeSudoCommand(cls, cmd): p = DmSubprocess('%s %s' % (cls.SUDO_CMD, cmd)) p.run() @classmethod def executeCommand(cls, cmd): p = DmSubprocess('%s' % (cmd)) p.run() def getLdapClient(self): if self.ldapClient is not None: try: self.ldapClient.simple_bind_s(self.adminDn, self.adminPassword) except Exception, ex: self.getLogger().error('Invalidating LDAP client due to error: %s' % ex) self.unbind(self.ldapClient) self.ldapClient = None if not self.ldapClient: self.ldapClient = self.bind(self.serverUrl, self.adminDn, self.adminPassword) return self.ldapClient @classmethod def bind(cls, serverUrl, adminDn, adminPassword): try: ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) ldapClient = ldap.initialize(serverUrl) ldapClient.set_option(ldap.OPT_REFERRALS,0) ldapClient.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3) ldapClient.simple_bind_s(adminDn, adminPassword) cls.getLogger().debug('Successful binding with LDAP DN: %s' % adminDn) return ldapClient except ldap.INVALID_CREDENTIALS, ex: ldapClient.unbind() raise AuthenticationError('Invalid LDAP credentials for admin user %s' % adminDn) except ldap.SERVER_DOWN, ex: raise CommunicationError('Cannot reach LDAP server %s' % serverUrl) except Exception, ex: raise InternalError('Unrecognized exception while binding to LDAP server %s: %s' % (serverUrl, ex)) @classmethod def unbind(cls, ldapClient): try: ldapClient.unbind() except Exception, ex: cls.getLogger().error('Could not unbind LDAP client: %s' % ex) def createGroup(self, name): """ Create group if it does not exist. """ logger = self.getLogger() ldapClient = self.getLdapClient() name = str(name) try: groupDn = self.groupDnFormat % name logger.debug('Looking for group DN: %s' % groupDn) # this method will throw exception if group is not found resultList = ldapClient.search_s(groupDn, ldap.SCOPE_BASE) groupTuple = resultList[0] logger.debug('Group %s already exists' % groupTuple[0]) return except ldap.NO_SUCH_OBJECT, ex: logger.debug('Group DN %s must be created' % groupDn) except Exception, ex: raise InternalError(exception=ex) # determine gidNumber: look through all entries to get max value, # then increment it # ldap should really be configured to handle gid's automatically, # and should prevent invalid entries try: logger.debug('Looking for max group id') resultList = ldapClient.search_s(self.groupBaseDn, ldap.SCOPE_ONELEVEL, attrlist=['gidNumber']) maxGid = 0 if self.minGidNumber: maxGid = self.minGidNumber for result in resultList: gidList = result[1].get('gidNumber', []) gid = 0 if gidList: gid = int(gidList[0]) if gid > maxGid: maxGid = gid gidNumber = str(maxGid + 1) logger.debug('Max GID is %s, new group id will be %s' % (maxGid, gidNumber)) except Exception, ex: raise InternalError(exception=ex) attrs = {} attrs['objectclass'] = ['posixGroup','top'] attrs['cn'] = name attrs['gidNumber'] = [gidNumber] attrs['memberUid'] = [] try: groupLdif = ldap.modlist.addModlist(attrs) ldapClient.add_s(groupDn, groupLdif) except Exception, ex: logger.error('Could not add group %s: %s' % (groupDn, ex)) raise InternalError(exception=ex) def addUserToGroup(self, username, groupName): """ Add user to group. """ logger = self.getLogger() ldapClient = self.getLdapClient() username = str(username) groupName = str(groupName) try: groupDn = self.groupDnFormat % groupName resultList = ldapClient.search_s(groupDn, ldap.SCOPE_BASE) groupTuple = resultList[0] groupAttrs = groupTuple[1] memberUidList = groupAttrs.get('memberUid', []) if username in memberUidList: logger.debug('Group %s already contains user %s' % (groupName, username)) return except Exception, ex: raise InternalError(exception=ex) logger.debug('Adding user %s to group %s' % (username, groupName)) memberUidList2 = copy.copy(memberUidList) memberUidList2.append(username) groupAttrs2 = copy.copy(groupAttrs) groupAttrs2['memberUid'] = memberUidList2 try: groupLdif = ldap.modlist.modifyModlist(groupAttrs, groupAttrs2) ldapClient.modify_s(groupDn, groupLdif) except Exception, ex: logger.error('Could not add user %s to group %s: %s' % (username, groupName, ex)) raise InternalError(exception=ex) # Refresh auth files self.refreshAuthFiles() def deleteUserFromGroup(self, username, groupName): """ Remove user from group. """ logger = self.getLogger() ldapClient = self.getLdapClient() username = str(username) groupName = str(groupName) try: groupDn = self.groupDnFormat % groupName resultList = ldapClient.search_s(groupDn, ldap.SCOPE_BASE) groupTuple = resultList[0] groupAttrs = groupTuple[1] memberUidList = groupAttrs.get('memberUid', []) if username not in memberUidList: logger.debug('Group %s does not contain user %s' % (groupName, username)) return except Exception, ex: raise InternalError(exception=ex) logger.debug('Removing user %s from group %s' % (username, groupName)) memberUidList2 = copy.copy(memberUidList) memberUidList2.remove(username) groupAttrs2 = copy.copy(groupAttrs) groupAttrs2['memberUid'] = memberUidList2 try: groupLdif = ldap.modlist.modifyModlist(groupAttrs, groupAttrs2) ldapClient.modify_s(groupDn, groupLdif) except Exception, ex: logger.error('Could not remove user %s from group %s: %s' % (username, groupName, ex)) raise InternalError(exception=ex) # Refresh auth files self.refreshAuthFiles() @classmethod def createLocalGroup(cls, name): """ Create local group if it does not exist. """ logger = cls.getLogger() try: group = grp.getgrnam(name) logger.debug('Group %s already exists' % name) return except KeyError, ex: # ok, we need to create group pass logger.debug('Creating group %s' % name) cmd = '%s %s' % (cls.GROUPADD_CMD, name) cls.executeSudoCommand(cmd) def addLocalUserToGroup(self, username, groupName): """ Add local user to group. """ self.addUserToGroup(username, groupName) def deleteLocalUserFromGroup(self, username, groupName): """ Remove local user from group. """ self.deleteUserFromGroup(username, groupName) def getGroupInfo(self, groupName): """ Get given group info. """ logger = self.getLogger() ldapClient = self.getLdapClient() groupName = str(groupName) try: groupDn = self.groupDnFormat % groupName resultList = ldapClient.search_s(groupDn, ldap.SCOPE_BASE) groupTuple = resultList[0] groupAttrs = groupTuple[1] return groupTuple except Exception, ex: raise InternalError(exception=ex) def setGroupUsers(self, groupName, usernameList): """ Set list of users for a given group. """ logger = self.getLogger() ldapClient = self.getLdapClient() groupName = str(groupName) try: groupDn = self.groupDnFormat % groupName resultList = ldapClient.search_s(groupDn, ldap.SCOPE_BASE) groupTuple = resultList[0] groupAttrs = groupTuple[1] except Exception, ex: raise InternalError(exception=ex) logger.debug('Setting users %s for group %s' % (usernameList, groupName)) memberUidList = [] for username in usernameList: memberUidList.append(str(username)) groupAttrs2 = copy.copy(groupAttrs) groupAttrs2['memberUid'] = memberUidList try: groupLdif = ldap.modlist.modifyModlist(groupAttrs, groupAttrs2) ldapClient.modify_s(groupDn, groupLdif) except Exception, ex: logger.error('Could not set users %s for group %s: %s' % (usernameList, groupName, ex)) raise InternalError(exception=ex) # Refresh auth files self.refreshAuthFiles() @classmethod def setPathReadExecutePermissionsForGroup(cls, path, groupName): """ Set path permissions for the given group. """ logger = cls.getLogger() logger.debug('Allowing group %s to read/execute path %s' % (groupName, path)) cmd = '%s -m group\:%s\:rx "%s"' % (cls.SETFACL_CMD, groupName, path) cls.executeSudoCommand(cmd) @classmethod def changePathGroupOwner(cls, path, groupName): logger = cls.getLogger() logger.debug('Changing group owner to %s for path %s' % (groupName, path)) cmd = '%s \:%s "%s"' % (cls.CHOWN_CMD, groupName, path) cls.executeSudoCommand(cmd) @classmethod def recursivelyChangePathGroupOwner(cls, path, groupName): logger = cls.getLogger() logger.debug('Recursively changing group owner to %s for path %s' % (groupName, path)) cmd = '%s -R \:%s "%s"' % (cls.CHOWN_CMD, groupName, path) cls.executeSudoCommand(cmd) @classmethod def refreshNscdGroupCache(cls): logger = cls.getLogger() try: logger.debug('Refreshing NCSD secondary group membership cache') cmd = '%s -i group' % (cls.NSCD_CMD) cls.executeSudoCommand(cmd) except Exception, ex: logger.warn('Failed to refresh NCSD group cache: %s' % (str(ex))) # Refresh auth files in a timer, to avoid running command too often def refreshAuthFiles(self): if not self.refreshAuthFilesCommand: return if self.refreshAuthFilesTimer and self.refreshAuthFilesTimer.is_alive(): return self.refreshAuthFilesTimer = threading.Timer(self.TIMER_DELAY_IN_SECONDS, self.__refreshAuthFiles) self.refreshAuthFilesTimer.start() def __refreshAuthFiles(self): logger = self.getLogger() try: logger.debug('Refreshing auth files') self.executeCommand(self.refreshAuthFilesCommand) except Exception, ex: logger.warn('Failed to refresh auth files: %s' % (str(ex))) @classmethod def chmodPathForFilesInDirectory(cls, directoryPath, fileMode): logger = cls.getLogger() logger.debug('Modifying permissions for all files in directory %s to %s' % (directoryPath, fileMode)) cmd = '%s %s -type f -exec chmod %s {} \;' % (cls.FIND_CMD, directoryPath, fileMode) cls.executeCommand(cmd) ####################################################################### # Testing. if __name__ == '__main__': utility = LdapLinuxPlatformUtility('ldaps://dmid-vm.xray.aps.anl.gov:636', 'uid=dmadmin,ou=People,o=aps.anl.gov,dc=aps,dc=anl,dc=gov', '/tmp/ldapPassword', groupDnFormat='cn=%s,ou=DM,ou=Group,o=aps.anl.gov,dc=aps,dc=anl,dc=gov', minGidNumber=66000) print utility.getGroupInfo(u's1id-test03') #utility.addLocalUserToGroup(u'sveseli', u'id8i-test02') #print utility.getGroupInfo(u'id8i-test02') #utility.deleteLocalUserFromGroup(u'sveseli', u'id8i-test02') #print utility.getGroupInfo(u'id8i-test02')