2
0
mirror of https://github.com/xcat2/confluent.git synced 2024-11-22 01:22:00 +00:00

Rework MFA handling

Avoid calling PAM in the parent process, as
this seems to cause problems with some PAM
configurations.
This commit is contained in:
Jarrod Johnson 2021-06-28 11:34:11 -04:00
parent f830514d10
commit 9c43dbff47
2 changed files with 25 additions and 19 deletions

View File

@ -26,6 +26,7 @@ import Cryptodome.Protocol.KDF as KDF
from fnmatch import fnmatch
import hashlib
import hmac
import msgpack
import multiprocessing
import os
import pwd
@ -100,6 +101,10 @@ _deniedbyrole = {
}
class PromptsNeeded(Exception):
def __init__(self, prompts):
self.prompts = prompts
def _get_usertenant(name, tenant=False):
"""_get_usertenant
@ -278,22 +283,34 @@ def check_user_passphrase(name, passphrase, operation=None, element=None, tenant
# to let a non-0 user check anothers password.
# We will fork and the child will assume elevated privilege to
# get unix_chkpwd helper to enable checking /etc/shadow
getprompt, sendprompt = os.pipe()
getprompt, sendprompt = os.fdopen(getprompt, 'rb', 0), os.fdopen(sendprompt, 'wb', 0)
pid = os.fork()
if not pid:
usergood = False
try:
getprompt.close()
# we change to the uid we are trying to authenticate as, because
# pam_unix uses unix_chkpwd which reque
os.setuid(pwe.pw_uid)
usergood = pam.authenticate(user, passphrase, service=_pamservice)
except pam.PromptsNeeded:
os._exit(2)
pa = pam.pam()
usergood = pa.authenticate(user, passphrase, service=_pamservice)
if (not usergood and len(pa.prompts) > 1 and
(not isinstance(passphrase, dict) or
(set(passphrase) - pa.prompts))):
sendprompt.write(msgpack.packb(list(pa.prompts)))
sendprompt.close()
os._exit(2)
finally:
os._exit(0 if usergood else 1)
sendprompt.close()
usergood = os.waitpid(pid, 0)[1]
if (usergood >> 8) == 2:
pam.authenticate(user, passphrase, service=_pamservice)
prompts = getprompt.read()
if (prompts):
raise PromptsNeeded(msgpack.unpackb(prompts))
usergood = usergood == 0
getprompt.close()
else:
# We are running as root, we don't need to fork in order to authenticate the
# user

View File

@ -104,17 +104,12 @@ pam_authenticate.restype = c_int
pam_authenticate.argtypes = [PamHandle, c_int]
class PromptsNeeded(Exception):
def __init__(self, prompts):
self.prompts = prompts
class pam():
code = 0
reason = None
def __init__(self):
pass
self.prompts = set([])
def authenticate(self, username, password, service='login', encoding='utf-8', resetcreds=True):
"""username and password authentication for the given service.
@ -145,17 +140,17 @@ class pam():
for i in range(n_messages):
if messages[i].contents.msg_style == PAM_PROMPT_ECHO_OFF:
currprompt = messages[i].contents.msg.decode('utf8').strip()
prompts.add(currprompt)
self.prompts.add(currprompt)
for i in range(n_messages):
if messages[i].contents.msg_style == PAM_PROMPT_ECHO_OFF:
currprompt = messages[i].contents.msg.decode('utf8').strip()
if isinstance(password, dict):
if currprompt in password:
continue
elif len(prompts) > 1:
elif len(self.prompts) > 1:
return 19 # PAM_CONV_ERR
else:
if len(prompts) > 1:
if len(self.prompts) > 1:
return 19 # PAM_CONV_ERR
# Create an array of n_messages response objects
addr = calloc(n_messages, sizeof(PamResponse))
@ -197,10 +192,6 @@ class pam():
self.reason = 'strings may not contain NUL'
return False
# do this up front so we can safely throw an exception if there's
# anything wrong with it
prompts = set([])
handle = PamHandle()
conv = PamConv(my_conv, 0)
retval = pam_start(service, username, byref(conv), byref(handle))
@ -229,8 +220,6 @@ class pam():
if hasattr(libpam, 'pam_end'):
pam_end(handle, retval)
if (not isinstance(password, dict)) and len(prompts) > 1 and not auth_success:
raise PromptsNeeded(prompts)
return auth_success