...
Here is the official main.py extauth file for a fully featured OpenLDAP integration.
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
import logging import redis import wsgiservice import json import hashlib from squirro.common.format import json from squirro.common.resource import Resource, StatusResource # noqa from squirro.common.dependency import get_injected from squirro.common.config import get_config from squirro_client import SquirroClient from ldap3 import Server, Connection, ALL log = logging.getLogger(__name__) config_extauth = get_config('squirro.service.extauth') config_redis = get_injected('config') @wsgiservice.mount('/v0/authenticate') class AuthenticateResource(Resource): def get_config_str(self, name, raise_on_error=True): value = config_extauth.get('extauth', name) if not value: log.error(u'Configuration error, failed to get %s parameter from extauth.ini ' \ '[extauth] section', name) self.deny() return value def get_config_int(self, name, raise_on_error=True): value = config_extauth.getint('extauth', name) if not value and value != 0: log.error(u'Configuration error, failed to get %s parameter from extauth.ini ' \ '[extauth] section', name) self.deny() return value def get_config_bool(self, name, raise_on_error=True): return config_extauth.getboolean('extauth', name) def md5(self, data): digest = hashlib.md5() digest.update(repr(data)) return digest.hexdigest() def deny(self, message="Access denied"): wsgiservice.raise_400(self, message) def get_ldap_conn(self): host = self.get_config_str('ldap_host') port = self.get_config_int('ldap_port') use_ssl = self.get_config_bool('ldap_use_ssl') username = self.get_config_str('ldap_username') password = self.get_config_str('ldap_password') server = Server(host=host, port=port, use_ssl=use_ssl) if username and password: conn = Connection(server=server, user=username, password=password, auto_bind=True) else: conn = Connection(server=server, auto_bind=True) return conn def get_squirro_client(self, squirro_client): if squirro_client: return squirro_client cluster = self.get_config_str('cluster') token = self.get_config_str('token') squirro_client = SquirroClient(None, None, cluster=cluster) squirro_client.authenticate(refresh_token=token) return squirro_client def create_group(self, name, squirro_client, redis_client): if not squirro_client: squirro_client = self.get_squirro_client(squirro_client) group = squirro_client.create_group(name) log.info('Created group %r with id %s', name, group['id']) #increment the cache key version self.cache_version += 1 redis_client.set(self.version_key, self.cache_version) return group['id'] def get_group_mapping(self, squirro_client, redis_client): """gets all groups from squirro and returns a dict with easy name lookup""" #cache key group_mapping_key = u"extauth_{0}_groupmapping_{1}".format(self.tenant, self.cache_version) #try redis cache first group_mapping = redis_client.get(group_mapping_key) if group_mapping: log.debug('Cache hit for redis key %s', group_mapping_key) group_mapping = json.loads(group_mapping) else: log.debug('Cache miss for redis key %s', group_mapping_key) if not squirro_client: squirro_client = self.get_squirro_client(squirro_client) groups_list = squirro_client.get_groups() #build the lookup: group_mapping = {} for group in groups_list: group_mapping[group['name']] = group['id'] #store for future lookups cache_ttl = self.get_config_str('cache_ttl') redis_client.set(group_mapping_key, json.dumps(group_mapping), ex=cache_ttl) log.debug('Squirro Group Mapping: %s', json.dumps(group_mapping, indent=2)) return group_mapping def POST(self): squirro_client = None r = redis.StrictRedis( host=config_redis.get('redis_cache', 'host'), port=config_redis.getint('redis_cache', 'port'), password=config_redis.get('redis_cache', 'password'), db=15) self.tenant = self.get_config_str('tenant') self.version_key = u"extauth_{0}_version".format(self.tenant) self.cache_version = r.get(self.version_key) if not self.cache_version: self.cache_version = 1 else: self.cache_version = int(self.cache_version) #extract data from http requests request = self.request.json_body log.debug(u'Received request:\n %s', json.dumps(request, indent=2)) headers_dict = request.get('headers', {}) #normalize headers headers = {} log.debug('Available http headers:') for name, value in headers_dict.iteritems(): normalized_name = name.lower().strip() headers[normalized_name] = value log.debug(u" - %s: %s", normalized_name, value) #extract the username from the http headers username_header = self.get_config_str('username_http_header') username_header = username_header.lower().strip() if not username_header: log.error('Configuration error, set username_http_header option in extauth.ini '\ '[extauth] section') self.deny() username = headers.get(username_header) log.debug(u'Username: %s', username) if not username: log.error(u'Cannot find header %s in available header names. Available headers are: %r', username_header, headers.keys()) self.deny() #get the list of all squirro groups squirro_group_mapping = self.get_group_mapping(squirro_client, r) groups = [] #handle default group default_group_name = self.get_config_str('default_group_name') if default_group_name: default_group_name = default_group_name.strip() default_group_id = squirro_group_mapping.get(default_group_name) if default_group_id: groups.append(default_group_id) log.debug(u'Granted default group membership %s/%s to user %s', default_group_name, default_group_id, username) else: log.error('Default group %r does not exist in Squirro. Create the group or ' \ 'adjust the default_group_name setting in the [extauth] section', default_group_name) self.deny() #establish ldap connection ldap_conn = self.get_ldap_conn() #get user attributes from ldap search_base = self.get_config_str('ldap_user_search_base') search_filter = self.get_config_str('ldap_user_filter').format(username=username) fullname_attribute = self.get_config_str('ldap_user_fullname_attribute') email_attribute = self.get_config_str('ldap_user_email_attribute') user_attributes = [fullname_attribute, email_attribute] ldap_conn.search(search_base, search_filter, attributes=user_attributes) if len(ldap_conn.entries) == 1: ldap_user = ldap_conn.response[0] user_dn = ldap_user['dn'] user_id = self.md5(user_dn) user_fullname = ldap_user['attributes'][fullname_attribute][0] user_email = ldap_user['attributes'][email_attribute][0] elif len(ldap_conn.entries) == 0: log.error('No users found for %r, %r', search_base, search_filter) self.deny() else: log.error('%i users found for %r, %r, a unique user must be matched', len(ldap_conn.entries), search_base, search_filter) self.deny() search_base = self.get_config_str('ldap_group_search_base', raise_on_error=False) search_filter = self.get_config_str('ldap_group_filter', raise_on_error=False) name_attribute = self.get_config_str('ldap_group_name_attribute', raise_on_error=False) #skip this step if not all attributes are there but a default group is already present if not search_base or not search_filter or not name_attribute: if len(groups) == 0: log.error(u'Cannot do group membership query for user %s, missing parameters', username) if not search_base: log.error('Configure ldap_group_search_base in [extauth] section') if not search_filter: log.error('Configure ldap_group_filter in [extauth] section') if not name_attribute: log.error('Configure ldap_group_name_attribute in [extauth] section') else: log.info('Skipping ldap group membership lookup, its not configured, but ' \ 'default group is present') else: #lookup groups in ldap for this user user_dict = { 'email': user_email, 'fullname': user_fullname, 'dn': user_dn } log.debug('Templating the group search filter with these keys: %s', user_dict.keys()) search_filter = search_filter.format(user=user_dict) ldap_conn.search(search_base, search_filter, attributes=[name_attribute]) create_groups = self.get_config_bool('create_squirro_groups', raise_on_error=False) for group in ldap_conn.response: group_name = group['attributes'][name_attribute][0] squirro_group_id = squirro_group_mapping.get(group_name) if squirro_group_id: groups.append(squirro_group_id) log.debug(u'Granted group membership %s/%s to user %s', group_name, squirro_group_id, username) elif create_groups: log.warn(u'LDAP group %r not present in Squirro, creating it...', group_name) squirro_group_id = self.create_group(group_name, squirro_client, r) log.debug(u'Granted group membership %s/%s to user %s', group_name, squirro_group_id, username) else: log.warn(u'LDAP group %r not present in Squirro, please create it', group_name) #finally deny access if not at least 1 group is present if len(groups) == 0: log.error(u'User %s has no group memberships, denying access', username) self.deny() log.info(u'Successful authentication for user %s (%s) with %i groups', username, user_email, len(groups)) retval = { 'user_id': user_id, 'user_information': {}, 'tenant': self.tenant, 'email': user_email, 'fullname': user_fullname, 'group_ids': groups } log.debug(u'Returning: \n %s', json.dumps(retval, indent=2)) return retval app = wsgiservice.get_app(globals()) |
...
Here is an example extauth.ini file that fits the setup we've used during this tutorial
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
[handler_file] application = extauth [extauth] # squirro tenant name tenant = squirro # define which http header contains the user id # the header name is normalized to lowercase username_http_header = X-Remote-User # squirro api token to retrieve group ids automatically # also used to create new groups automatically if enabled token = ... cluster = http://localhost:81 # hostname of the ldap / active directory server ldap_host = 127.0.0.1 # tcp port of the ldap server ldap_port = 389 # controls if ldap traffic is ssl encrypted or not # unless the host is localhost, encryption is highly recommended ldap_use_ssl = False # Ldap crendential, required if anonymous access is not allowed. ldap_username = cn=Manager,dc=acme,dc=com ldap_password = squirro # Search base and filter for locating the users # If 0 matches are found, access is denied ldap_user_search_base = ou=Users,dc=acme,dc=com ldap_user_filter = (& (objectclass=inetOrgPerson)(uid={username})) # Search base and filter for the group memberships # The query must be formulated so that only the group memberships of # the current user are returned # If no groups are found, access is denied or the default group is granted ldap_group_search_base = ou=Users,dc=acme,dc=com ldap_group_filter = (&(member={user[dn]})(objectClass=groupOfNames)) # mapping of the users ldap attributes to the squirro attributes ldap_user_id_attribute = dn ldap_user_fullname_attribute = cn ldap_user_email_attribute = mail # mapping of the group ldap name attribute to the squirro attributes ldap_group_name_attribute = cn # default group attributed to all users disregarding of the actual ldap group memberships # Fully optional, use this if only a valid user is required, but no ldap group membership default_group_name = Guests # if set, missing squirro groups are created automatically create_squirro_groups = True # how many seconds responses from Squirro and LDAP can be cached cache_ttl = 60 [logger_root] level = DEBUG |
...
Here a successful login for another user called bob with a few more groups:
Code Block | ||||
---|---|---|---|---|
| ||||
PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,636 DEBUG Received request: { "headers":{ "Content-Length":"", "X-Forwarded-Server":"127.0.0.1", "Accept-Language":"en-US,en;q=0.8,de-DE;q=0.6,de;q=0.4,es;q=0.2", "Accept-Encoding":"gzip, deflate, br", "X-Forwarded-Host":"192.168.110.228", "X-Remote-User":"joe", "X-Forwarded-For":"192.168.110.1", "Connection":"Keep-Alive", "Accept":"text\/html,application\/xhtml+xml,application\/xml;q=0.9,image\/webp,image\/apng,*\/*;q=0.8", "Upgrade-Insecure-Requests":"1", "Dnt":"1", "Host":"192.168.110.228", "Referer":"https:\/\/192.168.110.228\/app\/", "User-Agent":"Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_13_0) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/61.0.3163.100 Safari\/537.36", "Content-Type":"", "Authorization":"Basic am9lOnNxdWlycm8=" } } PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,636 DEBUG Available http headers: PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,637 DEBUG - content-length: PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,638 DEBUG - x-forwarded-server: 127.0.0.1 PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,638 DEBUG - accept-language: en-US,en;q=0.8,de-DE;q=0.6,de;q=0.4,es;q=0.2 PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,639 DEBUG - accept-encoding: gzip, deflate, br PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,640 DEBUG - x-forwarded-host: 192.168.110.228 PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,640 DEBUG - x-remote-user: joe PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,641 DEBUG - x-forwarded-for: 192.168.110.1 PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,641 DEBUG - connection: Keep-Alive PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,641 DEBUG - accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,642 DEBUG - upgrade-insecure-requests: 1 PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,642 DEBUG - dnt: 1 PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,643 DEBUG - host: 192.168.110.228 PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,643 DEBUG - referer: https://192.168.110.228/app/ PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,643 DEBUG - user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36 PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,643 DEBUG - content-type: PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,643 DEBUG - authorization: Basic am9lOnNxdWlycm8= PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,644 DEBUG Username: joe PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,644 DEBUG Cache hit for redis key extauth_squirro_groupmapping_1 PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,645 DEBUG Squirro Group Mapping: { "Sales":"ZervQb5RRne8IcbfFwU6Lw", "Marketing":"hvlU3UIVTNa_zr2r-4oOng", "Employees":"4L3K5yJJSHqOI_1gQ5cGsA", "Support":"teG8tDBkSz2FPjb6WHuqfQ", "Guests":"V9wGIL6jSYaWuWev_szFmQ" } PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,645 DEBUG Granted default group membership Guests/V9wGIL6jSYaWuWev_szFmQ to user joe PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,692 DEBUG Templating the group search filter with these keys: ['dn', 'fullname', 'email'] PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,694 DEBUG Granted group membership Sales/ZervQb5RRne8IcbfFwU6Lw to user joe PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,694 DEBUG Granted group membership Marketing/hvlU3UIVTNa_zr2r-4oOng to user joe PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,694 DEBUG Granted group membership Support/teG8tDBkSz2FPjb6WHuqfQ to user joe PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,695 INFO Successful authentication for user joe (joe@acme.com) with 4 groups PV:- Thread-2 squirro.service.extauth.main 2017-11-05 14:00:41,695 DEBUG Returning: { "group_ids":[ "V9wGIL6jSYaWuWev_szFmQ", "ZervQb5RRne8IcbfFwU6Lw", "hvlU3UIVTNa_zr2r-4oOng", "teG8tDBkSz2FPjb6WHuqfQ" ], "user_id":"0b54ffc786a0534af58cdc333c37f68a", "user_information":{ }, "fullname":"Joe Builder", "email":"joe@acme.com", "tenant":"squirro" } |
Deploying
In order to get the full version up and running, replace your existing extauth.ini and main.py files.
Adjust the settings to your needs.
The plugin depends on the python ldap3 module which is not delivered by Squirro out of the box.
Installing it is simple:
Code Block | ||||
---|---|---|---|---|
| ||||
source /opt/rh/python27/enable
source /opt/squirro/virtualenv/bin/activate
pip install ldap3 |
After the files and the module are in play, restart the service and tail all logs to see if there any issues:
Code Block |
---|
service sqextauthd restart; tail -f /var/log/squirro/extauth/*.log |
As you refine your extauth.ini, you have to rerun this command.
To force a reauthentication of your user, you can simply visit /app/#logout on the Squirro site.
This will destroy the current session and re-initalize the extauth process for the user. You should then see activity in the extauth log.