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
[email protected]
http://www.citrixonline.com
_______________________________________________
Freeipa-devel mailing list
[email protected]
https://www.redhat.com/mailman/listinfo/freeipa-devel