From c1324cd821ef005474eddd5d009e499de1a51ee3 Mon Sep 17 00:00:00 2001 From: Guillaume Abrioux Date: Thu, 14 Sep 2023 15:41:32 +0000 Subject: [PATCH] mgr/cephadm: add node-proxy endpoints to the mgr This adds 2 endpoints to the existing http agent endpoint: - '/node_proxy/idrac': support POST requests only although this endpoint is intended for fetching the idrac credentials of a given node. As we pass sensitive details (ceph secret) I didn't want to pass it as a query parameter in the url. Passing it in a HTTP header is perhaps a better approach but we already do similar thing for endpoint '/data' (agent) so for consistency reason I stick to that. - '/node_proxy/data': support GET and POST requests. A GET will return the aggregated data for all nodes within the cluster. node-proxy will use a POST request to that endpoint to push its collected data. Signed-off-by: Guillaume Abrioux --- src/cephadm/cephadm.py | 9 +++--- src/pybind/mgr/cephadm/agent.py | 56 +++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/cephadm/cephadm.py b/src/cephadm/cephadm.py index efdd85c9fdf..c0625a0f39b 100755 --- a/src/cephadm/cephadm.py +++ b/src/cephadm/cephadm.py @@ -1477,6 +1477,10 @@ class CephadmAgent(DaemonForm): self.pull_conf_settings() t_node_proxy = Thread(target=cephadmlib.node_proxy.server.main) + ssl_ctx = ssl.create_default_context() + ssl_ctx.check_hostname = True + ssl_ctx.verify_mode = ssl.CERT_REQUIRED + ssl_ctx.load_verify_locations(self.ca_path) t_node_proxy.start() try: @@ -1499,11 +1503,6 @@ class CephadmAgent(DaemonForm): if not self.volume_gatherer.is_alive(): self.volume_gatherer.start() - ssl_ctx = ssl.create_default_context() - ssl_ctx.check_hostname = True - ssl_ctx.verify_mode = ssl.CERT_REQUIRED - ssl_ctx.load_verify_locations(self.ca_path) - while not self.stop: start_time = time.monotonic() ack = self.ack diff --git a/src/pybind/mgr/cephadm/agent.py b/src/pybind/mgr/cephadm/agent.py index 93a08cb3439..40853f7806e 100644 --- a/src/pybind/mgr/cephadm/agent.py +++ b/src/pybind/mgr/cephadm/agent.py @@ -57,6 +57,12 @@ class AgentEndpoint: d.connect(name='host-data', route='/data/', controller=self.host_data.POST, conditions=dict(method=['POST'])) + d.connect(name='node-proxy-idrac', route='/node-proxy/idrac', + controller=self.host_data.node_proxy_idrac, + conditions=dict(method=['POST'])) + d.connect(name='node-proxy-data', route='/node-proxy/data', + controller=self.host_data.node_proxy_data, + conditions=dict(method=['GET','POST'])) cherrypy.tree.mount(None, '/', config={'/': {'request.dispatch': d}}) def configure_tls(self, server: Server) -> None: @@ -126,6 +132,56 @@ class HostData(Server): results['result'] = self.handle_metadata(data) return results + def validate_node_proxy_data(self, data: Dict[str, Any]) -> bool: + if 'host' not in data: + cherrypy.response.status = 400 + self.mgr.log.warning('The field \'host\' must be provided.') + elif 'keyring' not in data: + cherrypy.response.status = 400 + self.mgr.log.warning(f'The agent keyring must be provided.') + elif not self.mgr.agent_cache.agent_keys.get(data['host']): + cherrypy.response.status = 400 + self.mgr.log.warning(f'Make sure the agent is running on {data["host"]}') + elif data['keyring'] != self.mgr.agent_cache.agent_keys[data['host']]: + cherrypy.response.status = 403 + self.mgr.log.warning(f'Got wrong keyring from agent on host {data["host"]}.') + else: + cherrypy.response.status = 200 + + return cherrypy.response.status == 200 + + @cherrypy.tools.json_in() + @cherrypy.tools.json_out() + def node_proxy_idrac(self) -> Dict[str, Any]: + data: Dict[str, Any] = cherrypy.request.json + results: Dict[str, Any] = {} + + if self.validate_node_proxy_data(data): + idrac_details = self.mgr.get_store('node_proxy/idrac') + idrac_details_json = json.loads(idrac_details) + self.mgr.log.warning(f"{idrac_details_json}") + results['result'] = idrac_details_json[data["host"]] + + return results + + @cherrypy.tools.json_in() + @cherrypy.tools.json_out() + def node_proxy_data(self) -> Dict[str, Any]: + results: Dict[str, Any] = {} + + if cherrypy.request.method == 'POST': + data: Dict[str, Any] = cherrypy.request.json + if self.validate_node_proxy_data(data): + self.mgr.set_store(f'node_proxy/data/{data["host"]}', json.dumps(data['data'])) + self.mgr.log.warning(f"{data}") + results['result'] = data + + if cherrypy.request.method == 'GET': + for k, v in self.mgr.get_store_prefix('node_proxy/data').items(): + host = k.split('/')[-1:][0] + results[host] = json.loads(v) + return results + def check_request_fields(self, data: Dict[str, Any]) -> None: fields = '{' + ', '.join([key for key in data.keys()]) + '}' if 'host' not in data: -- 2.39.5