From f830514d10df4350a22346e0cf22ae975984385b Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Fri, 25 Jun 2021 17:26:32 -0400 Subject: [PATCH] Implement support for additional pam prompts For example, if PAM has OTP, then support it. --- confluent_server/confluent/auth.py | 12 ++++++--- confluent_server/confluent/httpapi.py | 9 +++++-- confluent_server/confluent/pam.py | 37 +++++++++++++++++++-------- imgutil/suse15/dracut/install | 2 +- 4 files changed, 43 insertions(+), 17 deletions(-) diff --git a/confluent_server/confluent/auth.py b/confluent_server/confluent/auth.py index dc17af8c..7bc167ff 100644 --- a/confluent_server/confluent/auth.py +++ b/confluent_server/confluent/auth.py @@ -221,11 +221,14 @@ def check_user_passphrase(name, passphrase, operation=None, element=None, tenant if ucfg is None: eventlet.sleep(0.05) return None + bpassphrase = None + if isinstance(passphrase, dict) and len(passphrase) == 1: + passphrase = list(passphrase.values())[0] if isinstance(passphrase, bytes): bpassphrase = passphrase - else: + elif not isinstance(passphrase, dict): bpassphrase = passphrase.encode('utf8') - if (user, tenant) in _passcache: + if (user, tenant) in _passcache and bpassphrase: if hashlib.sha256(bpassphrase).digest() == _passcache[(user, tenant)]: return authorize(user, element, tenant, operation=operation) else: @@ -233,7 +236,7 @@ def check_user_passphrase(name, passphrase, operation=None, element=None, tenant # while someone is legitimately logged in # invalidate cache and force the slower check del _passcache[(user, tenant)] - if 'cryptpass' in ucfg: + if 'cryptpass' in ucfg and bpassphrase: _passchecking[(user, tenant)] = True # TODO(jbjohnso): WORKERPOOL # PBKDF2 is, by design, cpu intensive @@ -296,7 +299,8 @@ def check_user_passphrase(name, passphrase, operation=None, element=None, tenant # user usergood = pam.authenticate(user, passphrase, service=_pamservice) if usergood: - _passcache[(user, tenant)] = hashlib.sha256(bpassphrase).digest() + if bpassphrase: + _passcache[(user, tenant)] = hashlib.sha256(bpassphrase).digest() return authorize(user, element, tenant, operation, skipuserobj=False) eventlet.sleep(0.05) # stall even on test for existence of a username return None diff --git a/confluent_server/confluent/httpapi.py b/confluent_server/confluent/httpapi.py index 2268983d..70438b4a 100644 --- a/confluent_server/confluent/httpapi.py +++ b/confluent_server/confluent/httpapi.py @@ -305,8 +305,13 @@ def _authorize_request(env, operation): # of a CSRF return {'code': 401} return ('logout',) - name, passphrase = base64.b64decode( - env['HTTP_AUTHORIZATION'].replace('Basic ', '')).split(b':', 1) + if env['HTTP_AUTHORIZATION'].startswith('MultiBasic '): + name, passphrase = base64.b64decode( + env['HTTP_AUTHORIZATION'].replace('MultiBasic ', '')).split(b':', 1) + passphrase = json.loads(passphrase) + else: + name, passphrase = base64.b64decode( + env['HTTP_AUTHORIZATION'].replace('Basic ', '')).split(b':', 1) try: authdata = auth.check_user_passphrase(name, passphrase, operation=operation, element=element) except Exception as e: diff --git a/confluent_server/confluent/pam.py b/confluent_server/confluent/pam.py index 5528f4f6..26df13e8 100644 --- a/confluent_server/confluent/pam.py +++ b/confluent_server/confluent/pam.py @@ -116,7 +116,7 @@ class pam(): def __init__(self): pass - def authenticate(self, username, password, service='login', encoding='utf-8', resetcreds=True, answers=None): + def authenticate(self, username, password, service='login', encoding='utf-8', resetcreds=True): """username and password authentication for the given service. Returns True for success, or False for failure. @@ -142,19 +142,38 @@ class pam(): def my_conv(n_messages, messages, p_response, app_data): """Simple conversation function that responds to any prompt where the echo is off with the supplied password""" + 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) + 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: + return 19 # PAM_CONV_ERR + else: + if len(prompts) > 1: + return 19 # PAM_CONV_ERR # Create an array of n_messages response objects addr = calloc(n_messages, sizeof(PamResponse)) response = cast(addr, POINTER(PamResponse)) p_response[0] = response for i in range(n_messages): if messages[i].contents.msg_style == PAM_PROMPT_ECHO_OFF: - prompts.add(messages[i].contents.msg) - if answers and messages[i].contents.msg in answers: - currpassword = answers[messages[i].contents.msg] - currcpassword = c_char_p(currpassword) + currprompt = messages[i].contents.msg.decode('utf8').strip() + if isinstance(password, dict): + if currprompt in password: + currpassword = password[currprompt] + currcpassword = c_char_p(currpassword.encode('utf8')) + elif len(password) == 1: + currpassword = list(password.values())[0] + currcpassword = c_char_p(currpassword.encode('utf8')) else: currpassword = password - currcpassword = cpassword + currcpassword = c_char_p(password.encode('utf8')) dst = calloc(len(currpassword)+1, sizeof(c_char)) memmove(dst, currcpassword, len(currpassword)) response[i].resp = dst @@ -164,7 +183,6 @@ class pam(): # python3 ctypes prefers bytes if sys.version_info >= (3,): if isinstance(username, str): username = username.encode(encoding) - if isinstance(password, str): password = password.encode(encoding) if isinstance(service, str): service = service.encode(encoding) else: if isinstance(username, unicode): @@ -174,14 +192,13 @@ class pam(): if isinstance(service, unicode): service = service.encode(encoding) - if b'\x00' in username or b'\x00' in password or b'\x00' in service: + if b'\x00' in username or b'\x00' in service: self.code = 4 # PAM_SYSTEM_ERR in Linux-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 - cpassword = c_char_p(password) prompts = set([]) handle = PamHandle() @@ -212,7 +229,7 @@ class pam(): if hasattr(libpam, 'pam_end'): pam_end(handle, retval) - if answers is None and len(prompts) > 1 and not auth_success: + if (not isinstance(password, dict)) and len(prompts) > 1 and not auth_success: raise PromptsNeeded(prompts) return auth_success diff --git a/imgutil/suse15/dracut/install b/imgutil/suse15/dracut/install index 38282550..8c8f905e 100644 --- a/imgutil/suse15/dracut/install +++ b/imgutil/suse15/dracut/install @@ -1,4 +1,4 @@ -dracut_install /usr/bin/mktemp +dracut_install mktemp dracut_install /usr/lib64/libtss2-tcti-device.so.* dracut_install tpm2_create tpm2_pcrread tpm2_createpolicy tpm2_createprimary dracut_install tpm2_load tpm2_unseal tpm2_getcap tpm2_evictcontrol