From: Guillaume Abrioux Date: Thu, 14 Sep 2023 15:41:32 +0000 (+0000) Subject: mgr/cephadm: add node-proxy endpoints to the mgr X-Git-Tag: v18.2.4~314^2~69 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=39a6be7571aeac9ea501eefe4ea97891370df092;p=ceph.git 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 (cherry picked from commit c1324cd821ef005474eddd5d009e499de1a51ee3) --- diff --git a/src/cephadm/cephadm.py b/src/cephadm/cephadm.py index 0674377a4162..44fc5be7adc8 100755 --- a/src/cephadm/cephadm.py +++ b/src/cephadm/cephadm.py @@ -4919,6 +4919,10 @@ WantedBy=ceph-{fsid}.target 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: @@ -4941,11 +4945,6 @@ WantedBy=ceph-{fsid}.target 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 93a08cb3439b..40853f7806ef 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: