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

Implement PAM user authentication and manage fs_uid in workspace module

parent 7d2c3307
...@@ -13,3 +13,4 @@ jsonschema ...@@ -13,3 +13,4 @@ jsonschema
tabulate tabulate
markdown markdown
passlib passlib
pexpect
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
"""Base authenticator class.""" """Base authenticator class."""
import logging import logging
import pexpect
from typing import Union from typing import Union
from zoe_api.auth.file import PlainTextAuthenticator from zoe_api.auth.file import PlainTextAuthenticator
...@@ -46,6 +47,26 @@ class BaseAuthenticator: ...@@ -46,6 +47,26 @@ class BaseAuthenticator:
return user return user
elif user.auth_source == "internal" and user.check_password(password): elif user.auth_source == "internal" and user.check_password(password):
return user return user
elif user.auth_source == "pam" and pam_authenticate(username, password):
return user
else: else:
log.error('Unknown auth source {} for user {}, cannot authenticate'.format(user.auth_source, user.username)) log.error('Unknown auth source {} for user {}, cannot authenticate'.format(user.auth_source, user.username))
return None 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."""
try:
child = pexpect.spawn('/bin/su', ['-', username])
child.expect('Password:')
child.sendline(password)
result = child.expect(['su: Authentication failure', username])
child.close()
except pexpect.TIMEOUT as err:
log.error("Error authenticating. Reason: {}".format(err))
return False
if result == 0:
return False
else:
return True
...@@ -39,6 +39,9 @@ class LoginWeb(ZoeWebRequestHandler): ...@@ -39,6 +39,9 @@ class LoginWeb(ZoeWebRequestHandler):
username = self.get_argument("username", "") username = self.get_argument("username", "")
password = self.get_argument("password", "") password = self.get_argument("password", "")
user = BaseAuthenticator().full_auth(username, password) user = BaseAuthenticator().full_auth(username, password)
if user is None:
self.redirect(self.get_argument("next", u"/login"))
return
if not self.get_secure_cookie('zoe'): if not self.get_secure_cookie('zoe'):
cookie_val = user.username cookie_val = user.username
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
"""Interface to PostgresQL for Zoe state.""" """Interface to PostgresQL for Zoe state."""
import logging import logging
import pwd
from passlib.hash import pbkdf2_sha256 as hash_algo from passlib.hash import pbkdf2_sha256 as hash_algo
...@@ -40,6 +41,9 @@ class User(BaseRecord): ...@@ -40,6 +41,9 @@ class User(BaseRecord):
self.role_id = d['role_id'] self.role_id = d['role_id']
self.quota_id = d['quota_id'] self.quota_id = d['quota_id']
if self.auth_source == "pam":
self.fs_uid = pwd.getpwnam(self.username).pw_uid
def serialize(self): def serialize(self):
"""Generates a dictionary that can be serialized in JSON.""" """Generates a dictionary that can be serialized in JSON."""
return { return {
......
...@@ -44,7 +44,7 @@ def gen_environment(execution: Execution, service: Service, env_subst_dict: Dict ...@@ -44,7 +44,7 @@ def gen_environment(execution: Execution, service: Service, env_subst_dict: Dict
env_list.append(('USER', execution.owner.username)) env_list.append(('USER', execution.owner.username))
env_list.append(('SERVICE_NAME', service.name)) env_list.append(('SERVICE_NAME', service.name))
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(('ZOE_WORKSPACE', wk_vol.mount_point))
env_list.append(('HOME', wk_vol.mount_point)) env_list.append(('HOME', wk_vol.mount_point))
return env_list return env_list
...@@ -54,8 +54,7 @@ def gen_volumes(service: Service, execution: Execution) -> List[VolumeDescriptio ...@@ -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.""" """Return the list of default volumes to be added to all containers."""
vol_list = service.volumes vol_list = service.volumes
fswk = ZoeFSWorkspace() wk_vol = ZoeFSWorkspace().get(execution.owner)
wk_vol = fswk.get(execution.owner.username)
vol_list.append(wk_vol) vol_list.append(wk_vol)
......
...@@ -16,10 +16,10 @@ ...@@ -16,10 +16,10 @@
"""Filesystem implementation for Zoe workspaces.""" """Filesystem implementation for Zoe workspaces."""
import logging import logging
import os.path import os
import zoe_lib.config as config import zoe_lib.config as config
from zoe_lib.state import VolumeDescriptionHostPath from zoe_lib.state import VolumeDescriptionHostPath, User
import zoe_master.workspace.base import zoe_master.workspace.base
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -48,6 +48,15 @@ class ZoeFSWorkspace(zoe_master.workspace.base.ZoeWorkspaceBase): ...@@ -48,6 +48,15 @@ class ZoeFSWorkspace(zoe_master.workspace.base.ZoeWorkspaceBase):
"""Get the volume mount point.""" """Get the volume mount point."""
return 'workspace' return 'workspace'
def get(self, username): def get(self, user: User):
"""Return a VolumeDescription for the user workspace.""" """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):
try:
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)))
else:
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