import sys
sys.argv=['']
-import arvados
-import os
-import syslog
-
-def auth_log(msg):
- """Send errors to default auth log"""
- syslog.openlog(facility=syslog.LOG_AUTH)
- syslog.syslog('arvados_pam: ' + msg)
- syslog.closelog()
-
-def config_file():
- return file('/etc/default/arvados_pam')
-
-def config():
- txt = config_file().read()
- c = dict()
- for x in txt.splitlines(False):
- if not x.strip().startswith('#'):
- kv = x.split('=', 2)
- c[kv[0].strip()] = kv[1].strip()
- return c
-
-class AuthEvent(object):
- def __init__(self, client_host, api_host, shell_host, username, token):
- self.client_host = client_host
- self.api_host = api_host
- self.shell_hostname = shell_host
- self.username = username
- self.token = token
- self.vm = None
- self.user = None
-
- def can_login(self):
- ok = False
- try:
- self.arv = arvados.api('v1', host=self.api_host, token=self.token, cache=None)
- self._lookup_vm()
- if self._check_login_permission():
- self.result = 'Authenticated'
- ok = True
- else:
- self.result = 'Denied'
- except Exception as e:
- self.result = 'Error: ' + repr(e)
- auth_log(self.message())
- return ok
-
- def _lookup_vm(self):
- """Load the VM record for this host into self.vm. Raise if not possible."""
-
- vms = self.arv.virtual_machines().list(filters=[['hostname','=',self.shell_hostname]]).execute()
- if vms['items_available'] > 1:
- raise Exception("ambiguous VM hostname matched %d records" % vms['items_available'])
- if vms['items_available'] == 0:
- raise Exception("VM hostname not found")
- self.vm = vms['items'][0]
- if self.vm['hostname'] != self.shell_hostname:
- raise Exception("API returned record with wrong hostname")
-
- def _check_login_permission(self):
- """Check permission to log in. Return True if permission is granted."""
- self._lookup_vm()
- self.user = self.arv.users().current().execute()
- filters = [
- ['link_class','=','permission'],
- ['name','=','can_login'],
- ['head_uuid','=',self.vm['uuid']],
- ['tail_uuid','=',self.user['uuid']]]
- for l in self.arv.links().list(filters=filters, limit=10000).execute()['items']:
- if (l['properties']['username'] == self.username and
- l['tail_uuid'] == self.user['uuid'] and
- l['head_uuid'] == self.vm['uuid'] and
- l['link_class'] == 'permission' and
- l['name'] == 'can_login'):
- return True
- return False
-
- def message(self):
- if len(self.token) > 40:
- log_token = self.token[0:15]
- else:
- log_token = '<invalid>'
- log_label = [self.client_host, self.api_host, self.shell_hostname, self.username, log_token]
- if self.vm:
- log_label += [self.vm.get('uuid')]
- if self.user:
- log_label += [self.user.get('uuid'), self.user.get('full_name')]
- return str(log_label) + ': ' + self.result
-
+from . import auth_event
def pam_sm_authenticate(pamh, flags, argv):
+ config = {}
+ config['arvados_api_host'] = argv[1]
+ config['virtual_machine_hostname'] = argv[2]
+ if len(argv) > 3:
+ for k in argv[3:]:
+ config[k] = True
+
try:
- user = pamh.get_user()
- except pamh.exception as e:
+ username = pamh.get_user(None)
+ except pamh.exception, e:
return e.pam_result
- if not user:
+ if not username:
return pamh.PAM_USER_UNKNOWN
try:
- resp = pamh.conversation(pamh.Message(pamh.PAM_PROMPT_ECHO_OFF, ''))
+ prompt = '' if config.get('noprompt') else 'Arvados API token: '
+ token = pamh.conversation(pamh.Message(pamh.PAM_PROMPT_ECHO_OFF, prompt)).resp
except pamh.exception as e:
return e.pam_result
- try:
- config = config()
- api_host = config['ARVADOS_API_HOST'].strip()
- shell_host = config['HOSTNAME'].strip()
- except Exception as e:
- auth_log("loading config: " + repr(e))
- return False
-
- if AuthEvent(pamh.rhost, api_host, shell_host, user, resp.resp).can_login():
+ if auth_event.AuthEvent(
+ config=config,
+ service=pamh.service,
+ client_host=pamh.rhost,
+ username=username,
+ token=token).can_login():
return pamh.PAM_SUCCESS
else:
return pamh.PAM_AUTH_ERR