Source code for flux_restful.auth.pam
import logging
import os
from typing import Optional
from sqlalchemy.orm import Session
from flux_restful.auth.base import (
AuthBackend,
AuthError,
Principal,
is_admin,
is_system_user,
)
from flux_restful.core.config import settings
logger = logging.getLogger("flux-restful")
[docs]class PamBackend(AuthBackend):
"""
Authenticate against the host's PAM stack (system accounts).
Requires the ``python-pam`` package. The PAM service is FLUX_PAM_SERVICE
(default "login"). Superusers are the accounts in FLUX_ADMIN_USERS. This
pairs naturally with multi-user mode, where jobs run as the system user.
"""
name = "pam"
supports_password = True
def __init__(self, authenticator=None):
# An object with authenticate(username, password, service=...) -> bool.
# Injectable so the backend can be tested without PAM.
self._authenticator = authenticator
[docs] def validate(self) -> None:
# Fail at startup, not at the first login, if python-pam is missing.
self._get_authenticator()
if os.getuid() != 0:
logger.warning(
"The pam auth backend can only verify other users' passwords when "
"the server runs as root (it is running as uid %s); only the server "
"user's own password will work.",
os.getuid(),
)
def _get_authenticator(self):
if self._authenticator is None:
try:
import pam
except ImportError as e:
raise AuthError(
"The pam auth backend requires the python-pam package."
) from e
self._authenticator = pam.pam()
return self._authenticator
[docs] def authenticate(
self, db: Session, username: str, password: str
) -> Optional[Principal]:
if not username or not password:
return None
try:
ok = self._get_authenticator().authenticate(
username, password, service=settings.pam_service
)
except Exception as e:
logger.warning("PAM authentication error for %s: %s", username, e)
ok = False
if not ok:
return None
return self.resolve(db, username)
[docs] def resolve(self, db: Session, username: str) -> Optional[Principal]:
# Only real, unprivileged system accounts can be principals
if not is_system_user(username):
return None
return Principal(
user_name=username, is_superuser=is_admin(username), backend=self.name
)