summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr/dashboard/services
diff options
context:
space:
mode:
Diffstat (limited to 'src/pybind/mgr/dashboard/services')
-rw-r--r--src/pybind/mgr/dashboard/services/auth.py70
-rw-r--r--src/pybind/mgr/dashboard/services/rgw_client.py110
2 files changed, 170 insertions, 10 deletions
diff --git a/src/pybind/mgr/dashboard/services/auth.py b/src/pybind/mgr/dashboard/services/auth.py
index f13963abf..3c6002312 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):
@@ -46,6 +48,54 @@ class JwtManager(object):
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:
cls.init()
@@ -59,13 +109,13 @@ class JwtManager(object):
'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 @@ class JwtManager(object):
@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 @@ class JwtManager(object):
)
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/services/rgw_client.py b/src/pybind/mgr/dashboard/services/rgw_client.py
index 5120806d8..aed702603 100644
--- a/src/pybind/mgr/dashboard/services/rgw_client.py
+++ b/src/pybind/mgr/dashboard/services/rgw_client.py
@@ -658,6 +658,30 @@ class RgwClient(RestClient):
http_status_code=error.status_code,
component='rgw')
+ @RestClient.api_get('/{bucket_name}?acl')
+ def get_acl(self, bucket_name, request=None):
+ # pylint: disable=unused-argument
+ try:
+ result = request(raw_content=True) # type: ignore
+ return result.decode("utf-8")
+ except RequestException as error:
+ msg = 'Error getting ACLs'
+ if error.status_code == 404:
+ msg = '{}: {}'.format(msg, str(error))
+ raise DashboardException(msg=msg,
+ http_status_code=error.status_code,
+ component='rgw')
+
+ @RestClient.api_put('/{bucket_name}?acl')
+ def set_acl(self, bucket_name, acl, request=None):
+ # pylint: disable=unused-argument
+ headers = {'x-amz-acl': acl}
+ try:
+ result = request(headers=headers) # type: ignore
+ except RequestException as e:
+ raise DashboardException(msg=str(e), component='rgw')
+ return result
+
@RestClient.api_get('/{bucket_name}?encryption')
def get_bucket_encryption(self, bucket_name, request=None):
# pylint: disable=unused-argument
@@ -702,6 +726,19 @@ class RgwClient(RestClient):
except RequestException as e:
raise DashboardException(msg=str(e), component='rgw')
+ @RestClient.api_put('/{bucket_name}?tagging')
+ def set_tags(self, bucket_name, tags, request=None):
+ # pylint: disable=unused-argument
+ try:
+ ET.fromstring(tags)
+ except ET.ParseError:
+ return "Data must be properly formatted"
+ try:
+ result = request(data=tags) # type: ignore
+ except RequestException as e:
+ raise DashboardException(msg=str(e), component='rgw')
+ return result
+
@RestClient.api_get('/{bucket_name}?object-lock')
def get_bucket_locking(self, bucket_name, request=None):
# type: (str, Optional[object]) -> dict
@@ -806,6 +843,9 @@ class RgwClient(RestClient):
logger.warning('Error listing roles with code %d: %s', code, err)
return []
+ for role in roles:
+ if 'PermissionPolicies' not in role:
+ role['PermissionPolicies'] = []
return roles
def create_role(self, role_name: str, role_path: str, role_assume_policy_doc: str) -> None:
@@ -852,6 +892,74 @@ class RgwClient(RestClient):
f' For more information about the format look at {link}')
raise DashboardException(msg=msg, component='rgw')
+ def get_role(self, role_name: str):
+ rgw_get_role_command = ['role', 'get', '--role-name', role_name]
+ code, role, _err = mgr.send_rgwadmin_command(rgw_get_role_command)
+ if code != 0:
+ raise DashboardException(msg=f'Error getting role with code {code}: {_err}',
+ component='rgw')
+ return role
+
+ def update_role(self, role_name: str, max_session_duration: str):
+ rgw_update_role_command = ['role', 'update', '--role-name',
+ role_name, '--max_session_duration', max_session_duration]
+ code, _, _err = mgr.send_rgwadmin_command(rgw_update_role_command,
+ stdout_as_json=False)
+ if code != 0:
+ raise DashboardException(msg=f'Error updating role with code {code}: {_err}',
+ component='rgw')
+
+ def delete_role(self, role_name: str) -> None:
+ rgw_delete_role_command = ['role', 'delete', '--role-name', role_name]
+ code, _, _err = mgr.send_rgwadmin_command(rgw_delete_role_command,
+ stdout_as_json=False)
+ if code != 0:
+ raise DashboardException(msg=f'Error deleting role with code {code}: {_err}',
+ component='rgw')
+
+ @RestClient.api_get('/{bucket_name}?policy')
+ def get_bucket_policy(self, bucket_name: str, request=None):
+ """
+ Gets the bucket policy for a bucket.
+ :param bucket_name: The name of the bucket.
+ :type bucket_name: str
+ :rtype: None
+ """
+ # pylint: disable=unused-argument
+
+ try:
+ request = request()
+ return request
+ except RequestException as e:
+ if e.content:
+ content = json_str_to_object(e.content)
+ if content.get(
+ 'Code') == 'NoSuchBucketPolicy':
+ return None
+ raise e
+
+ @RestClient.api_put('/{bucket_name}?policy')
+ def set_bucket_policy(self, bucket_name: str, policy: str, request=None):
+ """
+ Sets the bucket policy for a bucket.
+ :param bucket_name: The name of the bucket.
+ :type bucket_name: str
+ :param policy: The bucket policy.
+ :type policy: JSON Structured Document
+ :return: The bucket policy.
+ :rtype: Dict
+ """
+ # pylint: disable=unused-argument
+ try:
+ request = request(data=policy)
+ except RequestException as e:
+ if e.content:
+ content = json_str_to_object(e.content)
+ if content.get("Code") == "InvalidArgument":
+ msg = "Invalid JSON document"
+ raise DashboardException(msg=msg, component='rgw')
+ raise DashboardException(e)
+
def perform_validations(self, retention_period_days, retention_period_years, mode):
try:
retention_period_days = int(retention_period_days) if retention_period_days else 0
@@ -956,7 +1064,7 @@ class RgwMultisite:
def create_realm(self, realm_name: str, default: bool):
rgw_realm_create_cmd = ['realm', 'create']
cmd_create_realm_options = ['--rgw-realm', realm_name]
- if default != 'false':
+ if default:
cmd_create_realm_options.append('--default')
rgw_realm_create_cmd += cmd_create_realm_options
try: