summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/cephadm/cephadm.py20
-rw-r--r--src/cephadm/tests/test_cephadm.py3
-rw-r--r--src/pybind/mgr/cephadm/module.py18
-rw-r--r--src/pybind/mgr/dashboard/controllers/prometheus.py65
-rw-r--r--src/pybind/mgr/dashboard/tests/test_prometheus.py15
-rw-r--r--src/pybind/mgr/orchestrator/_interface.py4
-rw-r--r--src/pybind/mgr/orchestrator/module.py9
-rw-r--r--src/pybind/mgr/prometheus/module.py52
8 files changed, 121 insertions, 65 deletions
diff --git a/src/cephadm/cephadm.py b/src/cephadm/cephadm.py
index 5deaec55949..75ac3045c1e 100755
--- a/src/cephadm/cephadm.py
+++ b/src/cephadm/cephadm.py
@@ -2421,11 +2421,23 @@ def prepare_dashboard(
pathify(ctx.dashboard_crt.name): '/tmp/dashboard.crt:z',
pathify(ctx.dashboard_key.name): '/tmp/dashboard.key:z'
}
- cli(['dashboard', 'set-ssl-certificate', '-i', '/tmp/dashboard.crt'], extra_mounts=mounts)
- cli(['dashboard', 'set-ssl-certificate-key', '-i', '/tmp/dashboard.key'], extra_mounts=mounts)
else:
- logger.info('Generating a dashboard self-signed certificate...')
- cli(['dashboard', 'create-self-signed-cert'])
+ logger.info('Using certmgr to generate dashboard self-signed certificate...')
+ cert_key = json_loads_retry(lambda: cli(['orch', 'certmgr', 'generate-certificates', 'dashboard'],
+ verbosity=CallVerbosity.QUIET_UNLESS_ERROR))
+ mounts = {}
+ if cert_key:
+ cert_file = write_tmp(cert_key['cert'], uid, gid)
+ key_file = write_tmp(cert_key['key'], uid, gid)
+ mounts = {
+ cert_file.name: '/tmp/dashboard.crt:z',
+ key_file.name: '/tmp/dashboard.key:z'
+ }
+ else:
+ logger.error('Cannot generate certificates for Ceph dashboard.')
+
+ cli(['dashboard', 'set-ssl-certificate', '-i', '/tmp/dashboard.crt'], extra_mounts=mounts)
+ cli(['dashboard', 'set-ssl-certificate-key', '-i', '/tmp/dashboard.key'], extra_mounts=mounts)
logger.info('Creating initial admin user...')
password = ctx.initial_dashboard_password or generate_password()
diff --git a/src/cephadm/tests/test_cephadm.py b/src/cephadm/tests/test_cephadm.py
index 6a5f4c9f00c..9e0345fe758 100644
--- a/src/cephadm/tests/test_cephadm.py
+++ b/src/cephadm/tests/test_cephadm.py
@@ -282,7 +282,8 @@ class TestCephAdm(object):
@mock.patch('cephadmlib.firewalld.Firewalld', mock_bad_firewalld)
@mock.patch('cephadm.Firewalld', mock_bad_firewalld)
@mock.patch('cephadm.logger')
- def test_skip_firewalld(self, _logger, cephadm_fs):
+ @mock.patch('cephadm.json_loads_retry', return_value=None)
+ def test_skip_firewalld(self, _logger, _jlr, cephadm_fs):
"""
test --skip-firewalld actually skips changing firewall
"""
diff --git a/src/pybind/mgr/cephadm/module.py b/src/pybind/mgr/cephadm/module.py
index 85e496b556b..97a9404a31c 100644
--- a/src/pybind/mgr/cephadm/module.py
+++ b/src/pybind/mgr/cephadm/module.py
@@ -539,7 +539,6 @@ class CephadmOrchestrator(orchestrator.Orchestrator, MgrModule,
super(CephadmOrchestrator, self).__init__(*args, **kwargs)
self._cluster_fsid: str = self.get('mon_map')['fsid']
self.last_monmap: Optional[datetime.datetime] = None
- self.cert_mgr = CertMgr(self, self.get_mgr_ip())
# for serve()
self.run = True
@@ -675,6 +674,8 @@ class CephadmOrchestrator(orchestrator.Orchestrator, MgrModule,
self.cert_key_store = CertKeyStore(self)
self.cert_key_store.load()
+ self.cert_mgr = CertMgr(self, self.get_mgr_ip())
+
# ensure the host lists are in sync
for h in self.inventory.keys():
if h not in self.cache.daemons:
@@ -3086,6 +3087,14 @@ Then run the following:
return (user, password)
@handle_orch_error
+ def generate_certificates(self, module_name: str) -> Optional[Dict[str, str]]:
+ supported_moduels = ['dashboard', 'prometheus']
+ if module_name not in supported_moduels:
+ raise OrchestratorError(f'Unsupported modlue {module_name}. Supported moduels are: {supported_moduels}')
+ cert, key = self.cert_mgr.generate_cert(self.get_hostname(), self.get_mgr_ip())
+ return {'cert': cert, 'key': key}
+
+ @handle_orch_error
def set_prometheus_access_info(self, user: str, password: str) -> str:
self.set_store(PrometheusService.USER_CFG_KEY, user)
self.set_store(PrometheusService.PASS_CFG_KEY, password)
@@ -3297,13 +3306,6 @@ Then run the following:
self._kick_serve_loop()
return f'Removed setting {setting} from tuned profile {profile_name}'
- @handle_orch_error
- def service_discovery_dump_cert(self) -> str:
- root_cert = self.cert_key_store.get_cert('service_discovery_root_cert')
- if not root_cert:
- raise OrchestratorError('No certificate found for service discovery')
- return root_cert
-
def set_health_warning(self, name: str, summary: str, count: int, detail: List[str]) -> None:
self.health_checks[name] = {
'severity': 'warning',
diff --git a/src/pybind/mgr/dashboard/controllers/prometheus.py b/src/pybind/mgr/dashboard/controllers/prometheus.py
index 75a607d1e31..d0ad51c8f7d 100644
--- a/src/pybind/mgr/dashboard/controllers/prometheus.py
+++ b/src/pybind/mgr/dashboard/controllers/prometheus.py
@@ -32,35 +32,43 @@ class PrometheusReceiver(BaseController):
class PrometheusRESTController(RESTController):
def prometheus_proxy(self, method, path, params=None, payload=None):
# type (str, str, dict, dict)
- user, password, cert_file = self.get_access_info('prometheus')
- verify = cert_file.name if cert_file else Settings.PROMETHEUS_API_SSL_VERIFY
+ user, password, ca_cert_file, cert_file, key_file = self.get_access_info('prometheus')
+ verify = ca_cert_file.name if ca_cert_file else Settings.PROMETHEUS_API_SSL_VERIFY
+ cert = (cert_file.name, key_file.name) if cert_file and key_file else None
response = self._proxy(self._get_api_url(Settings.PROMETHEUS_API_HOST),
method, path, 'Prometheus', params, payload,
- user=user, password=password, verify=verify)
- if cert_file:
- cert_file.close()
- os.unlink(cert_file.name)
+ user=user, password=password, verify=verify,
+ cert=cert)
+ for f in [ca_cert_file, cert_file, key_file]:
+ if f:
+ f.close()
+ os.unlink(f.name)
return response
def alert_proxy(self, method, path, params=None, payload=None):
# type (str, str, dict, dict)
- user, password, cert_file = self.get_access_info('alertmanager')
- verify = cert_file.name if cert_file else Settings.ALERTMANAGER_API_SSL_VERIFY
+ user, password, ca_cert_file, cert_file, key_file = self.get_access_info('alertmanager')
+ verify = ca_cert_file.name if ca_cert_file else Settings.ALERTMANAGER_API_SSL_VERIFY
+ cert = (cert_file.name, key_file.name) if cert_file and key_file else None
response = self._proxy(self._get_api_url(Settings.ALERTMANAGER_API_HOST, version='v2'),
method, path, 'Alertmanager', params, payload,
- user=user, password=password, verify=verify, is_alertmanager=True)
- if cert_file:
- cert_file.close()
- os.unlink(cert_file.name)
+ user=user, password=password, verify=verify,
+ cert=cert, is_alertmanager=True)
+ for f in [ca_cert_file, cert_file, key_file]:
+ if f:
+ f.close()
+ os.unlink(f.name)
return response
def get_access_info(self, module_name):
- # type (str, str, str)
+ # type (str, str, str, str, srt)
if module_name not in ['prometheus', 'alertmanager']:
raise DashboardException(f'Invalid module name {module_name}', component='prometheus')
user = None
password = None
cert_file = None
+ pkey_file = None
+ ca_cert_file = None
orch_backend = mgr.get_module_option_ex('orchestrator', 'orchestrator')
if orch_backend == 'cephadm':
@@ -75,11 +83,25 @@ class PrometheusRESTController(RESTController):
user = access_info['user']
password = access_info['password']
certificate = access_info['certificate']
- cert_file = tempfile.NamedTemporaryFile(delete=False)
- cert_file.write(certificate.encode('utf-8'))
- cert_file.flush()
-
- return user, password, cert_file
+ ca_cert_file = tempfile.NamedTemporaryFile(delete=False)
+ ca_cert_file.write(certificate.encode('utf-8'))
+ ca_cert_file.flush()
+
+ cert_file = None
+ cert = mgr.get_localized_store("crt") # type: ignore
+ if cert is not None:
+ cert_file = tempfile.NamedTemporaryFile(delete=False)
+ cert_file.write(cert.encode('utf-8'))
+ cert_file.flush() # cert_tmp must not be gc'ed
+
+ pkey_file = None
+ pkey = mgr.get_localized_store("key") # type: ignore
+ if pkey is not None:
+ pkey_file = tempfile.NamedTemporaryFile(delete=False)
+ pkey_file.write(pkey.encode('utf-8'))
+ pkey_file.flush()
+
+ return user, password, ca_cert_file, cert_file, pkey_file
def _get_api_url(self, host, version='v1'):
return f'{host.rstrip("/")}/api/{version}'
@@ -88,7 +110,7 @@ class PrometheusRESTController(RESTController):
return ceph_service.CephService.send_command('mon', 'balancer status')
def _proxy(self, base_url, method, path, api_name, params=None, payload=None, verify=True,
- user=None, password=None, is_alertmanager=False):
+ user=None, password=None, is_alertmanager=False, cert=None):
# type (str, str, str, str, dict, dict, bool)
content = None
try:
@@ -96,10 +118,11 @@ class PrometheusRESTController(RESTController):
auth = HTTPBasicAuth(user, password) if user and password else None
response = requests.request(method, base_url + path, params=params,
json=payload, verify=verify,
+ cert=cert,
auth=auth)
- except Exception:
+ except Exception as e:
raise DashboardException(
- "Could not reach {}'s API on {}".format(api_name, base_url),
+ "Could not reach {}'s API on {} error {}".format(api_name, base_url, e),
http_status_code=404,
component='prometheus')
try:
diff --git a/src/pybind/mgr/dashboard/tests/test_prometheus.py b/src/pybind/mgr/dashboard/tests/test_prometheus.py
index 7ff3e5dc166..7f795a47450 100644
--- a/src/pybind/mgr/dashboard/tests/test_prometheus.py
+++ b/src/pybind/mgr/dashboard/tests/test_prometheus.py
@@ -39,7 +39,7 @@ class PrometheusControllerTest(ControllerTestCase):
mock_request.assert_called_with('GET',
self.prometheus_host_api + '/rules',
json=None, params={},
- verify=True, auth=None)
+ verify=True, cert=None, auth=None)
assert mock_mon_command.called
@patch("dashboard.controllers.prometheus.mgr.get_module_option_ex", return_value='cephadm')
@@ -55,7 +55,7 @@ class PrometheusControllerTest(ControllerTestCase):
self.prometheus_host_api + '/rules',
json=None,
params={},
- verify=True, auth=None)
+ verify=True, cert=None, auth=None)
assert not mock_mon_command.called
@patch("dashboard.controllers.prometheus.mgr.get_module_option_ex", lambda a, b, c=None: None)
@@ -63,14 +63,14 @@ class PrometheusControllerTest(ControllerTestCase):
with patch('requests.request') as mock_request:
self._get('/api/prometheus')
mock_request.assert_called_with('GET', self.alert_host_api + '/alerts',
- json=None, params={}, verify=True, auth=None)
+ json=None, params={}, verify=True, cert=None, auth=None)
@patch("dashboard.controllers.prometheus.mgr.get_module_option_ex", lambda a, b, c=None: None)
def test_get_silences(self):
with patch('requests.request') as mock_request:
self._get('/api/prometheus/silences')
mock_request.assert_called_with('GET', self.alert_host_api + '/silences',
- json=None, params={}, verify=True, auth=None)
+ json=None, params={}, verify=True, cert=None, auth=None)
@patch("dashboard.controllers.prometheus.mgr.get_module_option_ex", lambda a, b, c=None: None)
def test_add_silence(self):
@@ -78,7 +78,7 @@ class PrometheusControllerTest(ControllerTestCase):
self._post('/api/prometheus/silence', {'id': 'new-silence'})
mock_request.assert_called_with('POST', self.alert_host_api + '/silences',
params=None, json={'id': 'new-silence'},
- verify=True, auth=None)
+ verify=True, cert=None, auth=None)
@patch("dashboard.controllers.prometheus.mgr.get_module_option_ex", lambda a, b, c=None: None)
def test_update_silence(self):
@@ -86,14 +86,15 @@ class PrometheusControllerTest(ControllerTestCase):
self._post('/api/prometheus/silence', {'id': 'update-silence'})
mock_request.assert_called_with('POST', self.alert_host_api + '/silences',
params=None, json={'id': 'update-silence'},
- verify=True, auth=None)
+ verify=True, cert=None, auth=None)
@patch("dashboard.controllers.prometheus.mgr.get_module_option_ex", lambda a, b, c=None: None)
def test_expire_silence(self):
with patch('requests.request') as mock_request:
self._delete('/api/prometheus/silence/0')
mock_request.assert_called_with('DELETE', self.alert_host_api + '/silence/0',
- json=None, params=None, verify=True, auth=None)
+ json=None, params=None, verify=True, cert=None,
+ auth=None)
def test_silences_empty_delete(self):
with patch('requests.request') as mock_request:
diff --git a/src/pybind/mgr/orchestrator/_interface.py b/src/pybind/mgr/orchestrator/_interface.py
index b302f702fc4..7584fabec0f 100644
--- a/src/pybind/mgr/orchestrator/_interface.py
+++ b/src/pybind/mgr/orchestrator/_interface.py
@@ -793,6 +793,10 @@ class Orchestrator(object):
"""set prometheus access information"""
raise NotImplementedError()
+ def generate_certificates(self, module_name: str) -> OrchResult[Optional[Dict[str, str]]]:
+ """set prometheus access information"""
+ raise NotImplementedError()
+
def set_custom_prometheus_alerts(self, alerts_file: str) -> OrchResult[str]:
"""set prometheus custom alerts files and schedule reconfig of prometheus"""
raise NotImplementedError()
diff --git a/src/pybind/mgr/orchestrator/module.py b/src/pybind/mgr/orchestrator/module.py
index ecf10d90058..484c2f39e9c 100644
--- a/src/pybind/mgr/orchestrator/module.py
+++ b/src/pybind/mgr/orchestrator/module.py
@@ -1207,6 +1207,15 @@ class OrchestratorCli(OrchestratorClientMixin, MgrModule,
return _username, _password
+ @_cli_write_command('orch certmgr generate-certificates')
+ def _cert_mgr_generate_certificates(self, module_name: str) -> HandleCommandResult:
+ try:
+ completion = self.generate_certificates(module_name)
+ result = raise_if_exception(completion)
+ return HandleCommandResult(stdout=json.dumps(result))
+ except ArgumentError as e:
+ return HandleCommandResult(-errno.EINVAL, "", (str(e)))
+
@_cli_write_command('orch prometheus set-credentials')
def _set_prometheus_access_info(self, username: Optional[str] = None, password: Optional[str] = None, inbuf: Optional[str] = None) -> HandleCommandResult:
try:
diff --git a/src/pybind/mgr/prometheus/module.py b/src/pybind/mgr/prometheus/module.py
index 6f675e3be00..7a4bca70fa4 100644
--- a/src/pybind/mgr/prometheus/module.py
+++ b/src/pybind/mgr/prometheus/module.py
@@ -10,13 +10,14 @@ import time
import enum
from packaging import version # type: ignore
from collections import namedtuple
+import tempfile
from mgr_module import CLIReadCommand, MgrModule, MgrStandbyModule, PG_STATES, Option, ServiceInfoT, HandleCommandResult, CLIWriteCommand
from mgr_util import get_default_addr, profile_method, build_url
from orchestrator import OrchestratorClientMixin, raise_if_exception, OrchestratorError
from rbd import RBD
-from typing import DefaultDict, Optional, Dict, Any, Set, cast, Tuple, Union, List, Callable
+from typing import DefaultDict, Optional, Dict, Any, Set, cast, Tuple, Union, List, Callable, IO
LabelValues = Tuple[str, ...]
Number = Union[int, float]
@@ -616,6 +617,8 @@ class Module(MgrModule, OrchestratorClientMixin):
def __init__(self, *args: Any, **kwargs: Any) -> None:
super(Module, self).__init__(*args, **kwargs)
+ self.key_file: IO[bytes]
+ self.cert_file: IO[bytes]
self.metrics = self._setup_static_metrics()
self.shutdown_event = threading.Event()
self.collect_lock = threading.Lock()
@@ -1769,7 +1772,7 @@ class Module(MgrModule, OrchestratorClientMixin):
'cephadm', 'secure_monitoring_stack', False)
if cephadm_secure_monitoring_stack:
try:
- self.setup_cephadm_tls_config(server_addr, server_port)
+ self.setup_tls_config(server_addr, server_port)
return
except Exception as e:
self.log.exception(f'Failed to setup cephadm based secure monitoring stack: {e}\n',
@@ -1789,28 +1792,29 @@ class Module(MgrModule, OrchestratorClientMixin):
self.set_uri(build_url(scheme='http', host=self.get_server_addr(),
port=server_port, path='/'))
- def setup_cephadm_tls_config(self, server_addr: str, server_port: int) -> None:
- from cephadm.ssl_cert_utils import SSLCerts
- # the ssl certs utils uses a NamedTemporaryFile for the cert files
- # generated with generate_cert_files function. We need the SSLCerts
- # object to not be cleaned up in order to have those temp files not
- # be cleaned up, so making it an attribute of the module instead
- # of just a standalone object
- self.cephadm_monitoring_tls_ssl_certs = SSLCerts()
- host = self.get_mgr_ip()
- try:
- old_cert = self.get_store('root/cert')
- old_key = self.get_store('root/key')
- if not old_cert or not old_key:
- raise Exception('No old credentials for mgr-prometheus endpoint')
- self.cephadm_monitoring_tls_ssl_certs.load_root_credentials(old_cert, old_key)
- except Exception:
- self.cephadm_monitoring_tls_ssl_certs.generate_root_cert(host)
- self.set_store('root/cert', self.cephadm_monitoring_tls_ssl_certs.get_root_cert())
- self.set_store('root/key', self.cephadm_monitoring_tls_ssl_certs.get_root_key())
-
- cert_file_path, key_file_path = self.cephadm_monitoring_tls_ssl_certs.generate_cert_files(
- self.get_hostname(), host)
+ def setup_tls_config(self, server_addr: str, server_port: int) -> None:
+ from mgr_util import verify_tls_files
+ cmd = {'prefix': 'orch certmgr generate-certificates',
+ 'module_name': 'prometheus',
+ 'format': 'json'}
+ ret, out, err = self.mon_command(cmd)
+ if ret != 0:
+ self.log.error(f'mon command to generate-certificates failed: {err}')
+ return
+ elif out is None:
+ self.log.error('mon command to generate-certificates failed to generate certificates')
+ return
+
+ cert_key = json.loads(out)
+ self.cert_file = tempfile.NamedTemporaryFile()
+ self.cert_file.write(cert_key['cert'].encode('utf-8'))
+ self.cert_file.flush() # cert_tmp must not be gc'ed
+ self.key_file = tempfile.NamedTemporaryFile()
+ self.key_file.write(cert_key['key'].encode('utf-8'))
+ self.key_file.flush() # pkey_tmp must not be gc'ed
+
+ verify_tls_files(self.cert_file.name, self.key_file.name)
+ cert_file_path, key_file_path = self.cert_file.name, self.key_file.name
cherrypy.config.update({
'server.socket_host': server_addr,