From 15e11e6ea332d1413ba32e0fcd62c9361856580b Mon Sep 17 00:00:00 2001 From: Natalia Serrano Date: Thu, 24 Apr 2025 15:35:07 +0200 Subject: [PATCH] refs #1848 decorate oglive methods --- CHANGELOG.md | 6 + linux/debian/changelog | 6 + src/VERSION | 2 +- .../modules/server/ogAdmClient/__init__.py | 205 +++++++++--------- 4 files changed, 120 insertions(+), 99 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 016dd86..74a9163 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [4.0.0] - 2025-04-24 + +### Added + +- Authn/authz to the oglive agent + ## [3.3.0] - 2025-04-14 ### Added diff --git a/linux/debian/changelog b/linux/debian/changelog index 7d0619e..e8a1f28 100644 --- a/linux/debian/changelog +++ b/linux/debian/changelog @@ -1,3 +1,9 @@ +ogagent (4.0.0-1) stable; urgency=medium + + * Handle authn/authz in the oglive agent + + -- OpenGnsys developers Thu, 24 Apr 2025 13:28:57 +0200 + ogagent (3.3.0-1) stable; urgency=medium * Create an additional json log file diff --git a/src/VERSION b/src/VERSION index 15a2799..fcdb2e1 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -3.3.0 +4.0.0 diff --git a/src/opengnsys/modules/server/ogAdmClient/__init__.py b/src/opengnsys/modules/server/ogAdmClient/__init__.py index 4967ccc..0410c9b 100644 --- a/src/opengnsys/modules/server/ogAdmClient/__init__.py +++ b/src/opengnsys/modules/server/ogAdmClient/__init__.py @@ -33,14 +33,15 @@ """ import base64 -#import threading -#import time import os import signal +import string +import random import subprocess from pathlib import Path from urllib.parse import unquote +from opengnsys import VERSION from opengnsys.log import logger from opengnsys.workers import ogLiveWorker @@ -50,22 +51,41 @@ def check_secret (fnc): Decorator to check for received secret key and raise exception if it isn't valid. """ def wrapper (*args, **kwargs): - return fnc (*args, **kwargs) - #try: - # this, path, get_params, post_params, server = args - # # Accept "status" operation with no arguments or any function with Authorization header - # if fnc.__name__ == 'process_status' and not get_params: - # return fnc (*args, **kwargs) - # elif this.random == server.headers['Authorization']: - # return fnc (*args, **kwargs) - # else: - # raise Exception ('Unauthorized operation') - #except Exception as e: - # logger.error (str (e)) - # raise Exception (e) + try: + this, path, get_params, post_params, server = args + + if not server: ## this happens on startup, eg. onActivation->autoexecCliente->ejecutaArchivo->popup->check_secret + return fnc (*args, **kwargs) + + if this.random == server.headers['Authorization']: + return fnc (*args, **kwargs) + else: + raise Exception ('Unauthorized operation') + except Exception as e: + logger.error (str (e)) + raise Exception (e) return wrapper +# Check if operation is permitted +def execution_level(level): + def check_permitted(fnc): + def wrapper(*args, **kwargs): + levels = ['status', 'halt', 'full'] + this = args[0] + try: + if levels.index(level) <= levels.index(this.exec_level): + return fnc(*args, **kwargs) + else: + raise Exception('Unauthorized operation') + except Exception as e: + logger.debug (str(e)) + raise Exception(e) + + return wrapper + + return check_permitted + class ogAdmClientWorker (ogLiveWorker): name = 'ogAdmClient' # Module name REST = None # REST object @@ -78,60 +98,6 @@ class ogAdmClientWorker (ogLiveWorker): self.REST.sendMessage ('ogAdmClient/stopped', {'mac': self.mac, 'ip': self.IPlocal, 'idcentro': self.idcentro, 'idaula': self.idaula, 'idordenador': self.idordenador, 'nombreordenador': self.nombreordenador}) - #def processClientMessage (self, message, data): - # logger.debug ('Got OpenGnsys message from client: {}, data {}'.format (message, data)) - - #def onLogin (self, data): - # logger.warning ('in onLogin, should not happen') - - #def onLogout (self, user): - # logger.warning ('in onLogout, should not happen') - - #@check_secret - #def process_reboot (self, path, get_params, post_params, server): - # """ - # Launches a system reboot operation - # :param path: - # :param get_params: - # :param post_params: - # :param server: authorization header - # :return: JSON object {"op": "launched"} - # """ - # logger.debug ('Received reboot operation') - - # # Rebooting thread - # def rebt(): - # operations.reboot() - # threading.Thread (target=rebt).start() - # return {'op': 'launched'} - - #@check_secret - #def process_poweroff (self, path, get_params, post_params, server): - # """ - # Launches a system power off operation - # :param path: - # :param get_params: - # :param post_params: - # :param server: authorization header - # :return: JSON object {"op": "launched"} - # """ - # logger.debug ('Received poweroff operation') - - # # Powering off thread - # def pwoff(): - # time.sleep (2) - # operations.poweroff() - # threading.Thread (target=pwoff).start() - # return {'op': 'launched'} - - ## process_* are invoked from opengnsys/httpserver.py:99 "data = module.processServerMessage (path, get_params, post_params, self)" (via opengnsys/workers/server_worker.py) - ## process_client_* are invoked from opengnsys/service.py:123 "v.processClientMessage (message, json.loads (data))" (via opengnsys/workers/server_worker.py) - - - - - - @@ -202,7 +168,7 @@ class ogAdmClientWorker (ogLiveWorker): logger.warning ('Ha ocurrido algún problema en el proceso de inclusión del cliente') logger.error ('LeeConfiguracion() failed') return False - res = self.enviaMensajeServidor ('InclusionCliente', { 'cfg': self.cfg2obj (cfg) }) + res = self.enviaMensajeServidor ('InclusionCliente', { 'cfg': self.cfg2obj (cfg), 'secret': self.random, 'agent_version': VERSION }) logger.debug ('res ({})'.format (res)) ## RESPUESTA_InclusionCliente @@ -308,6 +274,9 @@ class ogAdmClientWorker (ogLiveWorker): def onActivation (self): super().onActivation() + self.exec_level = 'full' + self.random = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(32)) + logger.info ('Inicio de sesion') logger.info ('Abriendo sesión en el servidor de Administración') if (not self.inclusionCliente()): @@ -334,35 +303,10 @@ class ogAdmClientWorker (ogLiveWorker): logger.info ('onActivation ok') - @check_secret - def process_status (self, path, get_params, post_params, server): - logger.debug ('in process_status, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) - full_config = 'full-config' in post_params and post_params['full-config'] - thr_status = {} - for k in self.thread_list: - thr_status[k] = { - 'running': self.thread_list[k]['running'], - 'result': self.thread_list[k]['result'], - } - ret = { - 'nfn': 'RESPUESTA_status', - 'mac': self.mac, - 'st': 'OGL', - 'ip': self.IPlocal, - 'threads': thr_status, - } - if full_config: - cfg = self.LeeConfiguracion() - ret['cfg'] = self.cfg2obj (cfg) - return ret - @check_secret - def process_popup (self, path, get_params, post_params, server): - logger.debug ('in process_popup, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) - logger.debug ('type(post_params) "{}"'.format (type (post_params))) - ## in process_popup, should not happen, path "[]" get_params "{}" post_params "{'title': 'mi titulo', 'message': 'mi mensaje'}" server "" - ## type(post_params) "" - return {'debug':'test'} + + + def do_CrearImagen (self, post_params): for k in ['dsk', 'par', 'cpt', 'idi', 'nci', 'ipr', 'nfn', 'ids']: @@ -755,16 +699,54 @@ class ogAdmClientWorker (ogLiveWorker): + + + @execution_level('status') + def process_status (self, path, get_params, post_params, server): + logger.debug ('in process_status, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + full_config = 'full-config' in post_params and post_params['full-config'] + thr_status = {} + for k in self.thread_list: + thr_status[k] = { + 'running': self.thread_list[k]['running'], + 'result': self.thread_list[k]['result'], + } + ret = { + 'nfn': 'RESPUESTA_status', + 'mac': self.mac, + 'st': 'OGL', + 'ip': self.IPlocal, + 'threads': thr_status, + } + if full_config: + cfg = self.LeeConfiguracion() + ret['cfg'] = self.cfg2obj (cfg) + return ret + + @check_secret + def process_popup (self, path, get_params, post_params, server): + logger.debug ('in process_popup, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) + logger.debug ('type(post_params) "{}"'.format (type (post_params))) + ## in process_popup, should not happen, path "[]" get_params "{}" post_params "{'title': 'mi titulo', 'message': 'mi mensaje'}" server "" + ## type(post_params) "" + return {'debug':'test'} + + @execution_level('full') + @check_secret def process_Actualizar (self, path, get_params, post_params, server): logger.debug ('in process_Actualizar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) return self._long_running_job ('Actualizar', self.do_Actualizar, args=(post_params,)) + @execution_level('full') + @check_secret def process_Purgar (self, path, get_params, post_params, server): logger.debug ('in process_Purgar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) os.kill (os.getpid(), signal.SIGTERM) return {} #exit (0) ## ogAdmClient.c:905 + @execution_level('full') + @check_secret def process_Comando (self, path, get_params, post_params, server): logger.debug ('in process_Comando, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) return self._long_running_job ('Comando', self.do_Comando, args=(post_params,)) @@ -773,10 +755,14 @@ class ogAdmClientWorker (ogLiveWorker): logger.debug ('in process_Sondeo, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) return {} ## ogAdmClient.c:920 + @execution_level('full') + @check_secret def process_ConsolaRemota (self, path, get_params, post_params, server): logger.debug ('in process_ConsolaRemota, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) return self._long_running_job ('ConsolaRemota', self.do_ConsolaRemota, args=(post_params,)) + @execution_level('full') + @check_secret def process_Arrancar (self, path, get_params, post_params, server): logger.debug ('in process_Arrancar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) @@ -793,26 +779,38 @@ class ogAdmClientWorker (ogLiveWorker): } return self.respuestaEjecucionComando (cmd, 0, ids) + @execution_level('halt') + @check_secret def process_Apagar (self, path, get_params, post_params, server): logger.debug ('in process_Apagar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) return self._long_running_job ('Apagar', self.do_Apagar, args=(post_params,)) + @execution_level('halt') + @check_secret def process_Reiniciar (self, path, get_params, post_params, server): logger.debug ('in process_Reiniciar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) return self._long_running_job ('Reiniciar', self.do_Reiniciar, args=(post_params,)) + @execution_level('full') + @check_secret def process_IniciarSesion (self, path, get_params, post_params, server): logger.debug ('in process_IniciarSesion, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) return self._long_running_job ('IniciarSesion', self.do_IniciarSesion, args=(post_params,)) + @execution_level('full') + @check_secret def process_EjecutarScript (self, path, get_params, post_params, server): logger.debug ('in process_EjecutarScript, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) return self._long_running_job ('EjecutarScript', self.do_EjecutarScript, args=(post_params,)) + @execution_level('full') + @check_secret def process_EjecutaComandosPendientes (self, path, get_params, post_params, server): logger.debug ('in process_EjecutaComandosPendientes, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) return {'true':'true'} ## ogAdmClient.c:2138 + @execution_level('full') + @check_secret def process_CrearImagen (self, path, get_params, post_params, server): logger.debug ('in process_CrearImagen, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) logger.debug ('type(post_params) "{}"'.format (type (post_params))) @@ -828,6 +826,8 @@ class ogAdmClientWorker (ogLiveWorker): # logger.warning ('this method has been removed') # raise Exception ({ '_httpcode': 404, '_msg': 'This method has been removed' }) + @execution_level('full') + @check_secret def process_RestaurarImagen (self, path, get_params, post_params, server): logger.debug ('in process_RestaurarImagen, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) logger.debug ('type(post_params) "{}"'.format (type (post_params))) @@ -844,18 +844,27 @@ class ogAdmClientWorker (ogLiveWorker): # logger.warning ('this method has been removed') # raise Exception ({ '_httpcode': 404, '_msg': 'This method has been removed' }) + ## una partición + cache en disco de 30 Gb: + @execution_level('full') + @check_secret def process_Configurar (self, path, get_params, post_params, server): logger.debug ('in process_Configurar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) return self._long_running_job ('Configurar', self.do_Configurar, args=(post_params,)) + @execution_level('full') + @check_secret def process_InventarioHardware (self, path, get_params, post_params, server): logger.debug ('in process_InventarioHardware, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) return self._long_running_job ('InventarioHardware', self.do_InventarioHardware, args=(post_params,)) + @execution_level('full') + @check_secret def process_InventarioSoftware (self, path, get_params, post_params, server): logger.debug ('in process_InventarioSoftware, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) return self._long_running_job ('InventarioSoftware', self.do_InventarioSoftware, args=(post_params,)) + @execution_level('full') + @check_secret def process_KillJob (self, path, get_params, post_params, server): logger.debug ('in process_KillJob, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) jid = post_params['job_id'] -- 2.40.1