From 478c53ba68feb0448d9b4bb8be2f5012f7d85b9b Mon Sep 17 00:00:00 2001 From: Sage Weil Date: Thu, 8 Jul 2021 13:10:23 -0400 Subject: [PATCH] mgr/dashboard: add 'dashboard connect-rgw' command Signed-off-by: Sage Weil --- src/pybind/mgr/dashboard/module.py | 6 ++ .../mgr/dashboard/services/rgw_client.py | 82 +++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/src/pybind/mgr/dashboard/module.py b/src/pybind/mgr/dashboard/module.py index 802c5411137a8..512fd4ebcc1dd 100644 --- a/src/pybind/mgr/dashboard/module.py +++ b/src/pybind/mgr/dashboard/module.py @@ -29,6 +29,7 @@ from .controllers import generate_routes, json_error_page from .grafana import push_local_dashboards from .services.auth import AuthManager, AuthManagerTool, JwtManager from .services.exception import dashboard_exception_handler +from .services.rgw_client import configure_rgw_users from .services.sso import SSO_COMMANDS, handle_sso_command from .settings import handle_option_command, options_command_list, options_schema_list from .tools import NotificationQueue, RequestLoggingTool, TaskManager, \ @@ -406,6 +407,11 @@ class Module(MgrModule, CherryPyConfig): return result return 0, 'Self-signed certificate created', '' + @CLIWriteCommand("dashboard connect-rgw") + def connect_rgw(self): + configure_rgw_users() + return 0, '', '' + def handle_command(self, inbuf, cmd): # pylint: disable=too-many-return-statements res = handle_option_command(cmd, inbuf) diff --git a/src/pybind/mgr/dashboard/services/rgw_client.py b/src/pybind/mgr/dashboard/services/rgw_client.py index 9573d24580672..fd3ecdb5adeaf 100644 --- a/src/pybind/mgr/dashboard/services/rgw_client.py +++ b/src/pybind/mgr/dashboard/services/rgw_client.py @@ -1,8 +1,10 @@ # -*- coding: utf-8 -*- import ipaddress +import json import logging import re +import subprocess import xml.etree.ElementTree as ET # noqa: N814 from distutils.util import strtobool @@ -183,6 +185,86 @@ def _parse_frontend_config(config) -> Tuple[int, bool]: raise LookupError('Failed to determine RGW port from "{}"'.format(config)) +def radosgw_admin(args: List[str]) -> Tuple[int, str, str]: + try: + result = subprocess.run( + [ + 'radosgw-admin', + '-c', str(mgr.get_ceph_conf_path()), + '-k', str(mgr.get_ceph_option('keyring')), + '-n', f'mgr.{mgr.get_mgr_id()}', + ] + args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=10, + ) + return result.returncode, result.stdout.decode('utf-8'), result.stderr.decode('utf-8') + except subprocess.CalledProcessError as ex: + mgr.log.error(f'Error executing radosgw-admin {ex.cmd}: {ex.output}') + raise + except subprocess.TimeoutExpired as ex: + mgr.log.error(f'Timeout (10s) executing radosgw-admin {ex.cmd}') + raise + + +def configure_rgw_users(): + mgr.log.info('Configuring dashboard RGW credentials') + user = 'dashboard' + + def parse_secrets(user: str, out: str) -> Tuple[Optional[str], Optional[str]]: + r = json.loads(out) + for k in r.get('keys', []): + if k.get('user') == user and r.get('system') in ['true', True]: + access_key = k.get('access_key') + secret_key = k.get('secret_key') + return access_key, secret_key + return None, None + + def get_keys(realm: Optional[str]) -> Tuple[str, str]: + cmd = ['user', 'info', '--uid', user] + if realm: + cmd += ['--rgw-realm', realm] + rc, out, _ = radosgw_admin(cmd) + access_key = None + secret_key = None + if not rc: + access_key, secret_key = parse_secrets(user, out) + if not access_key: + rc, out, err = radosgw_admin([ + 'user', 'create', + '--uid', user, + '--display-name', 'Ceph Dashboard', + '--system', + ]) + if not rc: + access_key, secret_key = parse_secrets(user, out) + if not access_key: + mgr.log.error(f'Unable to create rgw {user} user: {err}') + assert access_key + assert secret_key + return access_key, secret_key + + rc, out, err = radosgw_admin(['realm', 'list']) + if rc: + mgr.log.error(f'Unable to list RGW realms: {err}') + return + j = json.loads(out) + realms = j.get('realms', []) + access_key = None + secret_key = None + if realms: + als = {} + sls = {} + for realm in realms: + als[realm], sls[realm] = get_keys(realm) + access_key = json.dumps(als) + secret_key = json.dumps(sls) + else: + access_key, secret_key = get_keys(None) + Settings.RGW_API_ACCESS_KEY = access_key + Settings.RGW_API_SECRET_KEY = secret_key + + class RgwClient(RestClient): _host = None _port = None -- 2.39.5