-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
repo: backport mgr/dashboard fix for python-cryptography issues
Removes the (limited) runtime usages of python-jwt, and therefore, python-cryptography from the mgr dashboard module. This should restore dashboard functionality, if used alongside a patched pyo3 for python-bcrypt. Upstream-Ref: ceph/ceph@33d8bef References: ceph/ceph#55689 References: https://tracker.ceph.com/issues/63529 References: #20 References: https://git.st8l.com/luxolus/pyo3/commit/44d6919168621b46e4e884130ca43338655b020c
- Loading branch information
Showing
1 changed file
with
321 additions
and
0 deletions.
There are no files selected for viewing
321 changes: 321 additions & 0 deletions
321
ceph-18.2.2-backport-mgr-dashboard-simplify-authentication-protocol.patch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,321 @@ | ||
From fcbd8fdae9d80d9a9bd61838aeaf41b504a5888a Mon Sep 17 00:00:00 2001 | ||
From: Daniel Persson <mailto.woden@gmail.com> | ||
Date: Wed, 29 Nov 2023 09:39:51 +0000 | ||
Subject: [PATCH 1/3] mgr/dashboard: Simplify authentication protocol By | ||
removing the dependency to PyJWT we also remove the dependency to the | ||
cryptographic library which in the dashboard module will create a crash. In | ||
newer implementations of the library PyO3 is used to run rust code in order | ||
to encrypt with Elliptic Curves. This is never used in the dashboard | ||
communication so a much simpler implementation where we only use the hmac | ||
sha256 algorithm to create the signed JWT message could be used. | ||
|
||
Fixes: https://forum.proxmox.com/threads/ceph-warning-post-upgrade-to-v8.129371 | ||
Signed-off-by: Daniel Persson <mailto.woden@gmail.com> | ||
(cherry picked from commit c616a9d017b5fcc85bb5c1556bccf4c77cc3899e) | ||
--- | ||
src/pybind/mgr/dashboard/constraints.txt | 1 - | ||
src/pybind/mgr/dashboard/exceptions.py | 12 ++++ | ||
src/pybind/mgr/dashboard/requirements.txt | 1 - | ||
src/pybind/mgr/dashboard/services/auth.py | 70 ++++++++++++++++++++--- | ||
src/pybind/mgr/dashboard/tox.ini | 1 + | ||
5 files changed, 74 insertions(+), 11 deletions(-) | ||
|
||
diff --git a/src/pybind/mgr/dashboard/constraints.txt b/src/pybind/mgr/dashboard/constraints.txt | ||
index 55f81c92dec06..fd6141048800a 100644 | ||
--- a/src/pybind/mgr/dashboard/constraints.txt | ||
+++ b/src/pybind/mgr/dashboard/constraints.txt | ||
@@ -1,6 +1,5 @@ | ||
CherryPy~=13.1 | ||
more-itertools~=8.14 | ||
-PyJWT~=2.0 | ||
bcrypt~=3.1 | ||
python3-saml~=1.4 | ||
requests~=2.26 | ||
diff --git a/src/pybind/mgr/dashboard/exceptions.py b/src/pybind/mgr/dashboard/exceptions.py | ||
index 96cbc52335613..d396a38d2c3a2 100644 | ||
--- a/src/pybind/mgr/dashboard/exceptions.py | ||
+++ b/src/pybind/mgr/dashboard/exceptions.py | ||
@@ -121,3 +121,15 @@ class GrafanaError(Exception): | ||
|
||
class PasswordPolicyException(Exception): | ||
pass | ||
+ | ||
+ | ||
+class ExpiredSignatureError(Exception): | ||
+ pass | ||
+ | ||
+ | ||
+class InvalidTokenError(Exception): | ||
+ pass | ||
+ | ||
+ | ||
+class InvalidAlgorithmError(Exception): | ||
+ pass | ||
diff --git a/src/pybind/mgr/dashboard/requirements.txt b/src/pybind/mgr/dashboard/requirements.txt | ||
index 8003d62a5523f..292971819c9c6 100644 | ||
--- a/src/pybind/mgr/dashboard/requirements.txt | ||
+++ b/src/pybind/mgr/dashboard/requirements.txt | ||
@@ -1,7 +1,6 @@ | ||
bcrypt | ||
CherryPy | ||
more-itertools | ||
-PyJWT | ||
pyopenssl | ||
requests | ||
Routes | ||
diff --git a/src/pybind/mgr/dashboard/services/auth.py b/src/pybind/mgr/dashboard/services/auth.py | ||
index f13963abffdd4..3c6002312524d 100644 | ||
--- a/src/pybind/mgr/dashboard/services/auth.py | ||
+++ b/src/pybind/mgr/dashboard/services/auth.py | ||
@@ -1,17 +1,19 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
+import base64 | ||
+import hashlib | ||
+import hmac | ||
import json | ||
import logging | ||
import os | ||
import threading | ||
import time | ||
import uuid | ||
-from base64 import b64encode | ||
|
||
import cherrypy | ||
-import jwt | ||
|
||
from .. import mgr | ||
+from ..exceptions import ExpiredSignatureError, InvalidAlgorithmError, InvalidTokenError | ||
from .access_control import LocalAuthenticator, UserDoesNotExist | ||
|
||
cherrypy.config.update({ | ||
@@ -33,7 +35,7 @@ class JwtManager(object): | ||
@staticmethod | ||
def _gen_secret(): | ||
secret = os.urandom(16) | ||
- return b64encode(secret).decode('utf-8') | ||
+ return base64.b64encode(secret).decode('utf-8') | ||
|
||
@classmethod | ||
def init(cls): | ||
@@ -45,6 +47,54 @@ def init(cls): | ||
mgr.set_store('jwt_secret', secret) | ||
cls._secret = secret | ||
|
||
+ @classmethod | ||
+ def array_to_base64_string(cls, message): | ||
+ jsonstr = json.dumps(message, sort_keys=True).replace(" ", "") | ||
+ string_bytes = base64.urlsafe_b64encode(bytes(jsonstr, 'UTF-8')) | ||
+ return string_bytes.decode('UTF-8').replace("=", "") | ||
+ | ||
+ @classmethod | ||
+ def encode(cls, message, secret): | ||
+ header = {"alg": cls.JWT_ALGORITHM, "typ": "JWT"} | ||
+ base64_header = cls.array_to_base64_string(header) | ||
+ base64_message = cls.array_to_base64_string(message) | ||
+ base64_secret = base64.urlsafe_b64encode(hmac.new( | ||
+ bytes(secret, 'UTF-8'), | ||
+ msg=bytes(base64_header + "." + base64_message, 'UTF-8'), | ||
+ digestmod=hashlib.sha256 | ||
+ ).digest()).decode('UTF-8').replace("=", "") | ||
+ return base64_header + "." + base64_message + "." + base64_secret | ||
+ | ||
+ @classmethod | ||
+ def decode(cls, message, secret): | ||
+ split_message = message.split(".") | ||
+ base64_header = split_message[0] | ||
+ base64_message = split_message[1] | ||
+ base64_secret = split_message[2] | ||
+ | ||
+ decoded_header = json.loads(base64.urlsafe_b64decode(base64_header)) | ||
+ | ||
+ if decoded_header['alg'] != cls.JWT_ALGORITHM: | ||
+ raise InvalidAlgorithmError() | ||
+ | ||
+ incoming_secret = base64.urlsafe_b64encode(hmac.new( | ||
+ bytes(secret, 'UTF-8'), | ||
+ msg=bytes(base64_header + "." + base64_message, 'UTF-8'), | ||
+ digestmod=hashlib.sha256 | ||
+ ).digest()).decode('UTF-8').replace("=", "") | ||
+ | ||
+ if base64_secret != incoming_secret: | ||
+ raise InvalidTokenError() | ||
+ | ||
+ # We add ==== as padding to ignore the requirement to have correct padding in | ||
+ # the urlsafe_b64decode method. | ||
+ decoded_message = json.loads(base64.urlsafe_b64decode(base64_message + "====")) | ||
+ now = int(time.time()) | ||
+ if decoded_message['exp'] < now: | ||
+ raise ExpiredSignatureError() | ||
+ | ||
+ return decoded_message | ||
+ | ||
@classmethod | ||
def gen_token(cls, username): | ||
if not cls._secret: | ||
@@ -59,13 +109,13 @@ def gen_token(cls, username): | ||
'iat': now, | ||
'username': username | ||
} | ||
- return jwt.encode(payload, cls._secret, algorithm=cls.JWT_ALGORITHM) # type: ignore | ||
+ return cls.encode(payload, cls._secret) # type: ignore | ||
|
||
@classmethod | ||
def decode_token(cls, token): | ||
if not cls._secret: | ||
cls.init() | ||
- return jwt.decode(token, cls._secret, algorithms=cls.JWT_ALGORITHM) # type: ignore | ||
+ return cls.decode(token, cls._secret) # type: ignore | ||
|
||
@classmethod | ||
def get_token_from_header(cls): | ||
@@ -99,8 +149,8 @@ def get_username(cls): | ||
@classmethod | ||
def get_user(cls, token): | ||
try: | ||
- dtoken = JwtManager.decode_token(token) | ||
- if not JwtManager.is_blocklisted(dtoken['jti']): | ||
+ dtoken = cls.decode_token(token) | ||
+ if not cls.is_blocklisted(dtoken['jti']): | ||
user = AuthManager.get_user(dtoken['username']) | ||
if user.last_update <= dtoken['iat']: | ||
return user | ||
@@ -110,10 +160,12 @@ def get_user(cls, token): | ||
) | ||
else: | ||
cls.logger.debug('Token is block-listed') # type: ignore | ||
- except jwt.ExpiredSignatureError: | ||
+ except ExpiredSignatureError: | ||
cls.logger.debug("Token has expired") # type: ignore | ||
- except jwt.InvalidTokenError: | ||
+ except InvalidTokenError: | ||
cls.logger.debug("Failed to decode token") # type: ignore | ||
+ except InvalidAlgorithmError: | ||
+ cls.logger.debug("Only the HS256 algorithm is supported.") # type: ignore | ||
except UserDoesNotExist: | ||
cls.logger.debug( # type: ignore | ||
"Invalid token: user %s does not exist", dtoken['username'] | ||
diff --git a/src/pybind/mgr/dashboard/tox.ini b/src/pybind/mgr/dashboard/tox.ini | ||
index 47756e946e125..271df286ec5e8 100644 | ||
--- a/src/pybind/mgr/dashboard/tox.ini | ||
+++ b/src/pybind/mgr/dashboard/tox.ini | ||
@@ -20,6 +20,7 @@ addopts = | ||
deps = | ||
-rrequirements.txt | ||
-cconstraints.txt | ||
+ PyJWT | ||
|
||
[base-test] | ||
deps = | ||
|
||
From d456590743aa26d7d253b20294f5aa33544ab8a7 Mon Sep 17 00:00:00 2001 | ||
From: Daniel Persson <mailto.woden@gmail.com> | ||
Date: Sun, 3 Dec 2023 08:03:47 +0000 | ||
Subject: [PATCH 2/3] mgr/dashboard: Changes suggested after review by | ||
@epuertat. | ||
|
||
Move the JWT requirement to the test requirements file. Also remove JWT from ceph specification and debian build. | ||
|
||
Signed-off-by: Daniel Persson <mailto.woden@gmail.com> | ||
(cherry picked from commit c1ea66fe12f86e7a63681cba860fb91b1ea86e12) | ||
--- | ||
ceph.spec.in | 4 ---- | ||
debian/control | 1 - | ||
src/pybind/mgr/dashboard/requirements-test.txt | 1 + | ||
src/pybind/mgr/dashboard/tox.ini | 1 - | ||
4 files changed, 1 insertion(+), 6 deletions(-) | ||
|
||
diff --git a/ceph.spec.in b/ceph.spec.in | ||
index ff8aa5aafbff8..c4281abc5bfbb 100644 | ||
--- a/ceph.spec.in | ||
+++ b/ceph.spec.in | ||
@@ -412,7 +412,6 @@ BuildRequires: xmlsec1-nss | ||
BuildRequires: xmlsec1-openssl | ||
BuildRequires: xmlsec1-openssl-devel | ||
BuildRequires: python%{python3_pkgversion}-cherrypy | ||
-BuildRequires: python%{python3_pkgversion}-jwt | ||
BuildRequires: python%{python3_pkgversion}-routes | ||
BuildRequires: python%{python3_pkgversion}-scipy | ||
BuildRequires: python%{python3_pkgversion}-werkzeug | ||
@@ -425,7 +424,6 @@ BuildRequires: libxmlsec1-1 | ||
BuildRequires: libxmlsec1-nss1 | ||
BuildRequires: libxmlsec1-openssl1 | ||
BuildRequires: python%{python3_pkgversion}-CherryPy | ||
-BuildRequires: python%{python3_pkgversion}-PyJWT | ||
BuildRequires: python%{python3_pkgversion}-Routes | ||
BuildRequires: python%{python3_pkgversion}-Werkzeug | ||
BuildRequires: python%{python3_pkgversion}-numpy-devel | ||
@@ -617,7 +615,6 @@ Requires: ceph-prometheus-alerts = %{_epoch_prefix}%{version}-%{release} | ||
Requires: python%{python3_pkgversion}-setuptools | ||
%if 0%{?fedora} || 0%{?rhel} | ||
Requires: python%{python3_pkgversion}-cherrypy | ||
-Requires: python%{python3_pkgversion}-jwt | ||
Requires: python%{python3_pkgversion}-routes | ||
Requires: python%{python3_pkgversion}-werkzeug | ||
%if 0%{?weak_deps} | ||
@@ -626,7 +623,6 @@ Recommends: python%{python3_pkgversion}-saml | ||
%endif | ||
%if 0%{?suse_version} | ||
Requires: python%{python3_pkgversion}-CherryPy | ||
-Requires: python%{python3_pkgversion}-PyJWT | ||
Requires: python%{python3_pkgversion}-Routes | ||
Requires: python%{python3_pkgversion}-Werkzeug | ||
Recommends: python%{python3_pkgversion}-python3-saml | ||
diff --git a/debian/control b/debian/control | ||
index 837a55a371670..e7b123ec381e7 100644 | ||
--- a/debian/control | ||
+++ b/debian/control | ||
@@ -91,7 +91,6 @@ Build-Depends: automake, | ||
python3-all-dev, | ||
python3-cherrypy3, | ||
python3-natsort, | ||
- python3-jwt <pkg.ceph.check>, | ||
python3-pecan <pkg.ceph.check>, | ||
python3-bcrypt <pkg.ceph.check>, | ||
tox <pkg.ceph.check>, | ||
diff --git a/src/pybind/mgr/dashboard/requirements-test.txt b/src/pybind/mgr/dashboard/requirements-test.txt | ||
index da283d0b64aaa..aa80b3336b540 100644 | ||
--- a/src/pybind/mgr/dashboard/requirements-test.txt | ||
+++ b/src/pybind/mgr/dashboard/requirements-test.txt | ||
@@ -2,3 +2,4 @@ pytest-cov | ||
pytest-instafail | ||
pyfakefs==4.5.0 | ||
jsonschema~=4.0 | ||
+PyJWT~=2.0 | ||
diff --git a/src/pybind/mgr/dashboard/tox.ini b/src/pybind/mgr/dashboard/tox.ini | ||
index 271df286ec5e8..47756e946e125 100644 | ||
--- a/src/pybind/mgr/dashboard/tox.ini | ||
+++ b/src/pybind/mgr/dashboard/tox.ini | ||
@@ -20,7 +20,6 @@ addopts = | ||
deps = | ||
-rrequirements.txt | ||
-cconstraints.txt | ||
- PyJWT | ||
|
||
[base-test] | ||
deps = | ||
|
||
From 04b3792228415fa0320eb2e28c60e00fac68d3d8 Mon Sep 17 00:00:00 2001 | ||
From: Daniel Persson <mailto.woden@gmail.com> | ||
Date: Sun, 3 Dec 2023 09:46:56 +0000 | ||
Subject: [PATCH 3/3] mgr/dashboard: Updated test dependencies | ||
|
||
Seemed that the test dependencies was separated in two different requirements files | ||
one for the testing and one for linting. Added the JWT dependency in the linting file | ||
as well. | ||
|
||
Signed-off-by: Daniel Persson <mailto.woden@gmail.com> | ||
(cherry picked from commit 06765e648acb1676d5d563c631b8d8fc08b5323c) | ||
--- | ||
src/pybind/mgr/dashboard/requirements-lint.txt | 1 + | ||
1 file changed, 1 insertion(+) | ||
|
||
diff --git a/src/pybind/mgr/dashboard/requirements-lint.txt b/src/pybind/mgr/dashboard/requirements-lint.txt | ||
index 57e5191574083..571c92a4ebfbc 100644 | ||
--- a/src/pybind/mgr/dashboard/requirements-lint.txt | ||
+++ b/src/pybind/mgr/dashboard/requirements-lint.txt | ||
@@ -9,3 +9,4 @@ autopep8==1.5.7 | ||
pyfakefs==4.5.0 | ||
isort==5.5.3 | ||
jsonschema~=4.0 | ||
+PyJWT~=2.0 |