Commit 605a18e2 authored by Daniele Venzano's avatar Daniele Venzano
Browse files

Implement PAM user authentication and manage fs_uid in workspace module

parent 7d2c3307
......@@ -16,6 +16,7 @@
"""Base authenticator class."""
import logging
import pexpect
from typing import Union
from zoe_api.auth.file import PlainTextAuthenticator
......@@ -46,6 +47,26 @@ class BaseAuthenticator:
return user
elif user.auth_source == "internal" and user.check_password(password):
return user
elif user.auth_source == "pam" and pam_authenticate(username, password):
return user
log.error('Unknown auth source {} for user {}, cannot authenticate'.format(user.auth_source, user.username))
return None
def pam_authenticate(username, password):
"""Use su for testing credentials. Using directly the PAM library would be more performant, but would also require Zoe to run as root."""
child = pexpect.spawn('/bin/su', ['-', username])
result = child.expect(['su: Authentication failure', username])
except pexpect.TIMEOUT as err:
log.error("Error authenticating. Reason: {}".format(err))
return False
if result == 0:
return False
return True
......@@ -39,6 +39,9 @@ class LoginWeb(ZoeWebRequestHandler):
username = self.get_argument("username", "")
password = self.get_argument("password", "")
user = BaseAuthenticator().full_auth(username, password)
if user is None:
self.redirect(self.get_argument("next", u"/login"))
if not self.get_secure_cookie('zoe'):
cookie_val = user.username
......@@ -16,6 +16,7 @@
"""Interface to PostgresQL for Zoe state."""
import logging
import pwd
from passlib.hash import pbkdf2_sha256 as hash_algo
......@@ -40,6 +41,9 @@ class User(BaseRecord):
self.role_id = d['role_id']
self.quota_id = d['quota_id']
if self.auth_source == "pam":
self.fs_uid = pwd.getpwnam(self.username).pw_uid
def serialize(self):
"""Generates a dictionary that can be serialized in JSON."""
return {
......@@ -44,7 +44,7 @@ def gen_environment(execution: Execution, service: Service, env_subst_dict: Dict
env_list.append(('USER', execution.owner.username))
wk_vol = ZoeFSWorkspace().get(execution.owner.username)
wk_vol = ZoeFSWorkspace().get(execution.owner)
env_list.append(('ZOE_WORKSPACE', wk_vol.mount_point))
env_list.append(('HOME', wk_vol.mount_point))
return env_list
......@@ -54,8 +54,7 @@ def gen_volumes(service: Service, execution: Execution) -> List[VolumeDescriptio
"""Return the list of default volumes to be added to all containers."""
vol_list = service.volumes
fswk = ZoeFSWorkspace()
wk_vol = fswk.get(execution.owner.username)
wk_vol = ZoeFSWorkspace().get(execution.owner)
......@@ -16,10 +16,10 @@
"""Filesystem implementation for Zoe workspaces."""
import logging
import os.path
import os
import zoe_lib.config as config
from zoe_lib.state import VolumeDescriptionHostPath
from zoe_lib.state import VolumeDescriptionHostPath, User
import zoe_master.workspace.base
log = logging.getLogger(__name__)
......@@ -48,6 +48,15 @@ class ZoeFSWorkspace(zoe_master.workspace.base.ZoeWorkspaceBase):
"""Get the volume mount point."""
return 'workspace'
def get(self, username):
def get(self, user: User):
"""Return a VolumeDescription for the user workspace."""
return VolumeDescriptionHostPath(path=self.get_path(username), name=self.get_mountpoint(), readonly=False)
if not self.exists(user.username):
os.makedirs(self.get_path(user.username), 0x700)
os.chown(self.get_path(user.username), user.fs_uid, config.get_conf().fs_group_id)
except OSError as e:
log.warning("Cannot create user workspace automatically: {}".format(str(e)))
if os.stat(self.get_path(user.username)).st_uid != user.fs_uid:
log.warning('The user fs_uid in the database does not match the workspace owner for user {}'.format(user.username))
return VolumeDescriptionHostPath(path=self.get_path(user.username), name=self.get_mountpoint(), readonly=False)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment