Attached is a pam_python module that can be used to perform FreeIPA HBAC authorization in conjunction with pam_python.so (http://ace-host.stuart.id.au/russell/files/pam_python/)
I have been working on this for a while as an alternative to sssd on systems that cannot support the sssd installation. There is no caching provided by this code, and is intended as a proof of concept or interim fix on a small scale. I have been craving a more formal c code approach to this general method, but am not adept in the c language. If anyone is feeling savoy, assistance in creating a more formal pam module would be very appreciated!
#!/usr/bin/env python # # pam_pyauth.py (Python LDAP RBAC) # # Requires Python 2.4 or Greater # # Copyright (c) 2010 Jr Aquino # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted (subject to the limitations in the # disclaimer below) provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the # distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT # HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import re import os import socket import syslog import ldap class LDAP(object): "This class is used for defining ldap.conf values and searching ldap" def __init__(self): # Initial Setup # Read in ldap.conf conf = open('/etc/ldap.conf', 'r').readlines() # Setup base variables self.binddn = None self.bindpw = None self.baseDN = None self.ugroupDN = None self.pwgroupDN = None self.sysgroupDN = None self.hostgroupDN = None self.ignore_users = [] self.ldap_servers = [] # Regex Definitions uri_check = uri_check = re.compile(r'uri ((ldap|ldaps)://(.*))') binddn_check = re.compile(r'binddn (.*)') bindpw_check = re.compile(r'bindpw (.*)') basedn_check = re.compile(r'base (.*)') ignore_check = re.compile(r'nss_initgroups_ignoreusers (.*)') ugroup_check = re.compile(r'nss_base_group (.*)') pwgroup_check = re.compile(r'nss_base_passwd (.*)') sysgroup_check = re.compile(r'nss_base_systemgroup (.*)') ldaphostgroup_check = re.compile(r'nss_base_hostgroup (.*)') rolegroup_check = re.compile(r'nss_base_rolegroup (.*)') ignore_users = [] ldap_servers = [] # Anonymously bind if no auth data present self.binddn = '' self.bindpw = '' # Parse ldap.conf for line in conf: binddn_match = binddn_check.search(line) bindpw_match = bindpw_check.search(line) basedn_match = basedn_check.search(line) uri_match = uri_check.search(line) ignore_match = ignore_check.search(line) ugroup_match = ugroup_check.search(line) pwgroup_match = pwgroup_check.search(line) sysgroup_match = sysgroup_check.search(line) hostgroup_match = ldaphostgroup_check.search(line) rolegroup_match = rolegroup_check.search(line) if binddn_match: self.binddn = binddn_match.group(1) if bindpw_match: self.bindpw = bindpw_match.group(1) if basedn_match: self.baseDN = basedn_match.group(1) if uri_match: self.ldap_servers = uri_match.group(1).split() if ignore_match: self.ignore_users = ignore_match.group(1).split(',') if ugroup_match: self.ugroupDN = ugroup_match.group(1) if pwgroup_match: self.pwgroupDN = pwgroup_match.group(1) if sysgroup_match: self.sysgroupDN = sysgroup_match.group(1) if hostgroup_match: self.hostgroupDN = hostgroup_match.group(1) if rolegroup_match: self.rolegroupDN = rolegroup_match.group(1) def Search(self, searchBase, attributes, searchFilter): "Search LDAP for Entries" # Search LDAP Servers for ldap_server in self.ldap_servers: results = [] try: l = ldap.initialize(ldap_server) ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW) l.protocol_version = ldap.VERSION3 l.simple_bind_s(self.binddn, self.bindpw) except ldap.SERVER_DOWN: continue searchScope = ldap.SCOPE_SUBTREE try: ldap_result_id = l.search(searchBase, searchScope, searchFilter, attributes) result_set = [] while 1: result_type, result_data = l.result(ldap_result_id, 0) if (result_data == []): break else: if result_type == ldap.RES_SEARCH_ENTRY: result_set.append(result_data) for item in result_set: for value in item: for line in value: if type(line) == str: dn = line elif type(line) == dict: data = line data['dn'] = [dn] search_results = (dn, data) results.append(data) results = tuple(results) return results except ldap.LDAPError, e: print e raise l.unbind_s() return def logOutput(message): "Send output to syslog" authpriv_notice = 86 syslog.syslog(authpriv_notice, message) return def pam_sm_acct_mgmt(pamh, flags, argv): """Check LDAP for Authorization PAM Python calls this method directly /etc/pam.d/system-auth: account required /lib/security/pam_python.so pam_pyauth.py """ # Initialize Class ldaplib = LDAP() # Initalize Syslog syslog.openlog('pam_pyauth') # Setup base variables hostname = socket.gethostname() usergroups = [] hostgroups = [] ignore_users = [] message = pamh.PAM_PERM_DENIED status = 'Failure' logmsg = 'Unknown Failure' # Enumerate Username try: user = pamh.get_user(None) except pamh.exception, e: return e.pam_result if user == None: return pamh.PAM_USER_UNKNOWN elif user in ldaplib.ignore_users: return pamh.PAM_SUCCESS else: # Search filter for posix group membership ugrp_search = '(member=uid=%s,%s)' % (user, ldaplib.pwgroupDN) # Search filter for hostgroup membership hgrp_search = '(member=fqdn=%s,%s)' % (hostname, ldaplib.sysgroupDN) # Perform the usergroup search and return list ldapusergroups = ldaplib.Search(ldaplib.ugroupDN, ['cn'], ugrp_search) # Perform the hostgroup search and return list ldaphostgroups = ldaplib.Search(ldaplib.hostgroupDN, ['cn'], hgrp_search) # Attempt to find nested usergroups if ldapusergroups: for ugrp in ldapusergroups: # Add the usergroups to the list usergroup = ugrp['dn'][0] usergroups.append(usergroup) # Search for nested usergroups nestsearch = ('member=%s' % (usergroup)) nestedldapgroups = ldaplib.Search(ldaplib.ugroupDN, ['cn'], nestsearch) #Add the nested usergroups to the list if nestedldapgroups: for nestedldapgroup in nestedldapgroups: nestedgroup = nestedldapgroup['dn'][0] usergroups.append(nestedgroup) # Attempt to find hostgroups if ldaphostgroups: for hostgroup in ldaphostgroups: hostgroup = hostgroup['dn'][0] hostgroups.append(hostgroup) # Search for nested hostgroups nestsearch = ('member=%s' % (hostgroup)) nestedldapgroups = ldaplib.Search(ldaplib.hostgroupDN, ['cn'], nestsearch) #Add the nested hostgroups to the list if nestedldapgroups: for nestedldapgroup in nestedldapgroups: nestedgroup = nestedldapgroup['dn'][0] hostgroups.append(nestedgroup) # Finally: Match usergroup and hostgroup in role object if usergroups and hostgroups: for usergroup in usergroups: role_search = 'memberUser=%s' % usergroup role_list = ldaplib.Search(ldaplib.rolegroupDN, None, role_search) if role_list: for role in role_list: role_name = role['cn'][0] accessRuleType = role['accessRuleType'][0] ipaEnabledFlag = role['ipaEnabledFlag'][0] role_host_groups = role['memberHost'] if (accessRuleType == 'allow' and ipaEnabledFlag == 'TRUE'): for hostgroup in role_host_groups: if hostgroup in hostgroups: message = pamh.PAM_SUCCESS status = 'Successful' logmsg = ('Authorization %s: ' '(User=%s Role=%s Usergroup=%s ' 'Hostgroup=%s)' % (status, user, role_name, usergroup, hostgroup)) break else: logmsg = ('Authorization %s: ' '(User=%s, Usergroups=%s, Hostgroups=%s, ' 'accessRuleType=%s, ipaEnabledFlag=%s)' % (status, user, str(usergroups), str(hostgroups), accessRuleType, ipaEnabledFlag)) elif logmsg == 'Unknown Failure': logmsg = ('Authorization %s: No Role (User=%s' ' Usergroups=%s Hostgroups=%s)' % (status, user, str(usergroups), str(hostgroups))) else: logmsg = ('Authorization %s: (User=%s Usergroups=%s ' 'Hostgroups=%s)' % (status, user, str(usergroups), str(hostgroups))) logOutput(logmsg) return message if __name__ == '__main__': print ('This is a pam module and is designed to be called from the ' 'pam_python.so library.\n' '(http://ace-host.stuart.id.au/russell/files/pam_python/)')
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Jr Aquino, GCIH | Information Security Specialist Citrix Online | 7408 Hollister Avenue | Goleta, CA 93117 T: +1 805.690.3478 jr.aqu...@citrixonline.com http://www.citrixonline.com
_______________________________________________ Freeipa-devel mailing list Freeipa-devel@redhat.com https://www.redhat.com/mailman/listinfo/freeipa-devel