mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge pull request #458 from pvanheus/auth_pam
AuthProvider for PAM (Pluggable Authentication Modules)
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
"""
|
||||
Created on 13/07/2015
|
||||
|
||||
Author Peter van Heusden (pvh@sanbi.ac.za)
|
||||
"""
|
||||
|
||||
import logging
|
||||
from distutils import spawn
|
||||
from subprocess import Popen, PIPE
|
||||
import shlex
|
||||
|
||||
from ..providers import AuthProvider
|
||||
from galaxy.auth import _get_bool
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
"""
|
||||
This module provides an AuthProvider for PAM (pluggable authentication module) authentication.
|
||||
PAM is the Pluggable Authentication Module system (http://www.linux-pam.org/)
|
||||
It relies on python-pam (https://pypi.python.org/pypi/python-pam)
|
||||
|
||||
Configuration is via config/auth_conf.xml and the following options are supported:
|
||||
- auto-register: True/False: automatically register an account for an unknown user. Default: False
|
||||
- maildomain: string: all valid users fall within the specified mail domain. Default: None
|
||||
- login-use-email: True/False: Parse the email address to get login details. Default: False
|
||||
- login-use-username: True/False: Use the username argument for login details. Default: False
|
||||
Technical note: when a user is not found in the database,
|
||||
their username is the user part of a user@host email
|
||||
address. After user creation, however, the username is
|
||||
the user's public name.
|
||||
- pam-service: string: The service name to use for PAM authentication. Default: galaxy
|
||||
- use-external-helper: True/False: Run an external helper script as root with sudo to do
|
||||
authentication. If False authentication is done
|
||||
by the module directly. Default: False
|
||||
Technical note: some PAM modules (e.g. pam_unix.so)
|
||||
require to be run as root to authenticate users.
|
||||
- authentication-helper-script: string: Absolute path to helper script to run for authentication. Default: None
|
||||
There needs to be a config (in /etc/sudoers or /etc/sudoers.d)
|
||||
that allows the galaxy user to run this as root with no password check
|
||||
For example:
|
||||
galaxy ALL=(root) NOPASSWD: /opt/galaxy/scripts/auth/pam_auth_helper.py
|
||||
|
||||
|
||||
Configuration example (for internal authentication, use email for user details):
|
||||
<authenticator>
|
||||
<type>PAM</type>
|
||||
<options>
|
||||
<auto-register>True</auto-register>
|
||||
<maildomain>example.com</maildomain>
|
||||
<login-use-email>True</login-use-email>
|
||||
<pam-service>ssh</pam-service>
|
||||
</options>
|
||||
</authenticator>
|
||||
"""
|
||||
|
||||
class PAM(AuthProvider):
|
||||
|
||||
plugin_type = 'PAM'
|
||||
|
||||
def authenticate(self, email, username, password, options):
|
||||
pam_username = None
|
||||
auto_register_username = None
|
||||
auto_register_email = None
|
||||
force_fail = False
|
||||
log.debug("use username: {} use email {} email {} username {}".format(options.get('login-use-username'), options.get('login-use-email', False), email, username))
|
||||
# check email based login first because if email exists in Galaxy DB
|
||||
# we will be given the "public name" as username
|
||||
if _get_bool(options, 'login-use-email', False) == True and email is not None:
|
||||
if '@' in email:
|
||||
(email_user, email_domain) = email.split('@')
|
||||
pam_username = email_user
|
||||
if email_domain == options.get('maildomain', None):
|
||||
auto_register_email = email
|
||||
if username is not None:
|
||||
auto_register_username = username
|
||||
else:
|
||||
auto_register_username = email_user
|
||||
else:
|
||||
log.debug('PAM authenticate: warning: email does not match configured PAM maildomain')
|
||||
# no need to fail: if auto-register is not enabled, this
|
||||
# might still be a valid user
|
||||
else:
|
||||
log.debug('PAM authenticate: email must be used to login, but no valid email found')
|
||||
force_fail = True
|
||||
elif _get_bool(options, 'login-use-username', False):
|
||||
# if we get here via authenticate_user then
|
||||
# user will be "public name" and
|
||||
# email address will be as per registered user
|
||||
if username is not None:
|
||||
pam_username = username
|
||||
if email is not None:
|
||||
auto_register_email = email
|
||||
elif options.get('maildomain', None) is not None:
|
||||
# we can register a user with this username and mail domain
|
||||
# if auto registration is enabled
|
||||
auto_register_email = '{}@{}'.format(username, options['maildomain'])
|
||||
auto_register_username = username
|
||||
else:
|
||||
log.debug('PAM authenticate: username login selected but no username provided')
|
||||
force_fail = True
|
||||
else:
|
||||
log.debug('PAM authenticate: could not find username for PAM')
|
||||
force_fail = True
|
||||
|
||||
if force_fail:
|
||||
return None, '', ''
|
||||
|
||||
pam_service = options.get('pam-service', 'galaxy')
|
||||
use_helper = _get_bool(options, 'use-external-helper', False)
|
||||
log.debug("PAM auth: will use external helper: {}".format(use_helper))
|
||||
authenticated = False
|
||||
if use_helper:
|
||||
authentication_helper = options.get('authentication-helper-script', '/bin/false').strip()
|
||||
log.debug("PAM auth: external helper script: {}".format(authentication_helper))
|
||||
if not authentication_helper.startswith('/'):
|
||||
# don't accept relative path
|
||||
authenticated = False
|
||||
else:
|
||||
auth_cmd = shlex.split('/usr/bin/sudo -n {}'.format(authentication_helper))
|
||||
log.debug("PAM auth: external helper cmd: {}".format(auth_cmd))
|
||||
proc = Popen(auth_cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
||||
message = '{}\n{}\n{}\n'.format(pam_service, pam_username, password)
|
||||
(output, error) = proc.communicate(message)
|
||||
status = proc.wait()
|
||||
if status != 0 and error != '':
|
||||
log.debug("PAM auth: external authentication script had errors: status {} error {}".format(status, error))
|
||||
if output.strip() == 'True':
|
||||
authenticated = True
|
||||
else:
|
||||
authenticated = False
|
||||
else:
|
||||
try:
|
||||
import pam
|
||||
except ImportError:
|
||||
log.debug('PAM authenticate: could not load pam module, PAM authentication disabled')
|
||||
return None, '', ''
|
||||
|
||||
p_auth = pam.pam()
|
||||
authenticated = p_auth.authenticate(pam_username, password, service=pam_service)
|
||||
|
||||
if authenticated:
|
||||
log.debug('PAM authentication successful for {}'.format(pam_username))
|
||||
return True, auto_register_email, auto_register_username
|
||||
else:
|
||||
log.debug('PAM authentication failed for {}'.format(pam_username))
|
||||
return False, '', ''
|
||||
|
||||
def authenticate_user(self, user, password, options):
|
||||
return self.authenticate(user.email, user.username, password, options)[0]
|
||||
|
||||
__all__ = ['PAM']
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
import signal
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
TIMEOUT = 5
|
||||
|
||||
try:
|
||||
import pam
|
||||
except ImportError:
|
||||
log.debug('PAM auth helper: Could not import pam module')
|
||||
sys.exit(1)
|
||||
|
||||
def handle_timeout(signum, stack):
|
||||
raise IOError("Timed out reading input")
|
||||
|
||||
# set timeout so we don't block on reading stdin
|
||||
signal.alarm(TIMEOUT)
|
||||
|
||||
pam_service = sys.stdin.readline().strip()
|
||||
pam_username = sys.stdin.readline().strip()
|
||||
pam_password = sys.stdin.readline().strip()
|
||||
|
||||
# cancel the alarm
|
||||
signal.alarm(0)
|
||||
|
||||
p_auth = pam.pam()
|
||||
authenticated = p_auth.authenticate(pam_username, pam_password, service=pam_service)
|
||||
if authenticated:
|
||||
log.debug('PAM auth helper: authentication successful for {}'.format(pam_username))
|
||||
sys.stdout.write('True\n')
|
||||
sys.exit(0)
|
||||
else:
|
||||
log.debug('PAM auth helper: authentication failed for {}'.format(pam_username))
|
||||
sys.stdout.write('False\n')
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user