From 7482f052f66e72a3e266b570152f14d9cb8c8094 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 20 Aug 2019 20:31:10 +0200 Subject: [PATCH] Get rid of gnupg, just store a hash of the id:token to reauthenticate later using session info --- README.md | 1 - debian/control | 1 - doc/requirements.txt | 1 - locales/en.json | 1 + moulinette/authenticators/__init__.py | 116 +++++++++++++------------- 5 files changed, 60 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index c3464927..75b8e2d5 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,6 @@ Requirements * Python 2.7 * python-bottle (>= 0.10) -* python-gnupg (>= 0.3) * python-ldap (>= 2.4) * PyYAML diff --git a/debian/control b/debian/control index 55d63073..b98a13e5 100644 --- a/debian/control +++ b/debian/control @@ -13,7 +13,6 @@ Depends: ${misc:Depends}, ${python:Depends}, python-ldap, python-yaml, python-bottle (>= 0.12), - python-gnupg, python-gevent-websocket, python-argcomplete, python-toml, diff --git a/doc/requirements.txt b/doc/requirements.txt index 142c12e7..c41bd5e9 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,5 +1,4 @@ sphinx -gnupg mock pyyaml toml diff --git a/locales/en.json b/locales/en.json index eda296ea..be014f6a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -16,6 +16,7 @@ "instance_already_running": "An instance is already running", "invalid_argument": "Invalid argument '{argument}': {error}", "invalid_password": "Invalid password", + "invalid_token": "Invalid token - please authenticate", "invalid_usage": "Invalid usage, pass --help to see help", "ldap_attribute_already_exists": "Attribute '{attribute}' already exists with value '{value}'", "ldap_operation_error": "An error occurred during LDAP operation", diff --git a/moulinette/authenticators/__init__.py b/moulinette/authenticators/__init__.py index 09bc209e..f5c46b9e 100644 --- a/moulinette/authenticators/__init__.py +++ b/moulinette/authenticators/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- import os -import gnupg import logging +import hashlib from moulinette.cache import open_cachefile, get_cachedir from moulinette.core import MoulinetteError @@ -88,44 +88,53 @@ class BaseAuthenticator(object): """ if self.is_authenticated: return self - store_session = True if password and token else False - if token: + # + # Authenticate using the password + # + if password: try: - # Extract id and actual token - s_id, s_token = token - except TypeError as e: - logger.error("unable to extract token parts from '%s' because '%s'", token, e) - if password is None: - raise MoulinetteError('error_see_log') - - logger.info("session will not be stored") - store_session = False - else: - if password is None: - # Retrieve session - password = self._retrieve_session(s_id, s_token) - - try: - # Attempt to authenticate - self.authenticate(password) - except MoulinetteError: - raise - except Exception as e: - logger.exception("authentication (name: '%s', vendor: '%s') fails because '%s'", - self.name, self.vendor, e) - raise MoulinetteError('unable_authenticate') - - # Store session - if store_session: - try: - self._store_session(s_id, s_token, password) + # Attempt to authenticate + self.authenticate(password) + except MoulinetteError: + raise except Exception as e: - import traceback - traceback.print_exc() - logger.exception("unable to store session because %s", e) - else: - logger.debug("session has been stored") + logger.exception("authentication (name: '%s', vendor: '%s') fails because '%s'", + self.name, self.vendor, e) + raise MoulinetteError('unable_authenticate') + + # Store session for later using the provided (new) token if any + if token: + try: + s_id, s_token = token + self._store_session(s_id, s_token) + except Exception as e: + import traceback + traceback.print_exc() + logger.exception("unable to store session because %s", e) + else: + logger.debug("session has been stored") + + # + # Authenticate using the token provided + # + elif token: + try: + s_id, s_token = token + # Attempt to authenticate + self._authenticate_session(s_id, s_token) + except MoulinetteError: + raise + except Exception as e: + logger.exception("authentication (name: '%s', vendor: '%s') fails because '%s'", + self.name, self.vendor, e) + raise MoulinetteError('unable_authenticate') + + # + # No credentials given, can't authenticate + # + else: + raise MoulinetteError('unable_authenticate') return self @@ -136,36 +145,31 @@ class BaseAuthenticator(object): return open_cachefile('%s.asc' % session_id, mode, subdir='session/%s' % self.name) - def _store_session(self, session_id, session_token, password): - """Store a session and its associated password""" - gpg = gnupg.GPG() - gpg.encoding = 'utf-8' - - # Encrypt the password using the session token - s = str(gpg.encrypt(password, None, symmetric=True, passphrase=session_token)) - assert len(s), "For some reason GPG can't perform encryption, maybe check /root/.gnupg/gpg.conf or re-run with gpg = gnupg.GPG(verbose=True) ?" + def _store_session(self, session_id, session_token): + """Store a session to be able to use it later to reauthenticate""" + # We store a hash of the session_id and the session_token (the token is assumed to be secret) + to_hash = "{id}:{token}".format(id=session_id, token=session_token) + hash_ = hashlib.sha256sum(to_hash).hexdigest() with self._open_sessionfile(session_id, 'w') as f: - f.write(s) + f.write(hash_) - def _retrieve_session(self, session_id, session_token): + def _authenticate_session(self, session_id, session_token): """Retrieve a session and return its associated password""" try: with self._open_sessionfile(session_id, 'r') as f: - enc_pwd = f.read() + stored_hash = f.read() except IOError as e: logger.debug("unable to retrieve session", exc_info=1) raise MoulinetteError('unable_retrieve_session', exception=e) else: - gpg = gnupg.GPG() - gpg.encoding = 'utf-8' + to_hash = "{id}:{token}".format(id=session_id, token=session_token) + hash_ = hashlib.sha256sum(to_hash).hexdigest() - decrypted = gpg.decrypt(enc_pwd, passphrase=session_token) - if decrypted.ok is not True: - error_message = "unable to decrypt password for the session: %s" % decrypted.status - logger.error(error_message) - raise MoulinetteError('unable_retrieve_session', exception=error_message) - return decrypted.data + if hash_ != stored_hash: + raise MoulinetteError('invalid_token') + else: + return def _clean_session(self, session_id): """Clean a session cache @@ -181,5 +185,3 @@ class BaseAuthenticator(object): os.remove(os.path.join(sessiondir, self.name, '%s.asc' % session_id)) except OSError: pass - -