diff --git a/CHANGELOG.md b/CHANGELOG.md index 74a9163..496eacc 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). +## [5.0.0] - 2025-04-25 + +### Added + +- Use TLS + ## [4.0.0] - 2025-04-24 ### Added diff --git a/linux/debian/changelog b/linux/debian/changelog index e8a1f28..66ff7e3 100644 --- a/linux/debian/changelog +++ b/linux/debian/changelog @@ -1,3 +1,9 @@ +ogagent (5.0.0-1) stable; urgency=medium + + * Use TLS + + -- OpenGnsys developers Fri, 25 Apr 2025 13:09:49 +0200 + ogagent (4.0.0-1) stable; urgency=medium * Handle authn/authz in the oglive agent diff --git a/src/VERSION b/src/VERSION index fcdb2e1..0062ac9 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -4.0.0 +5.0.0 diff --git a/src/cfg/ogagent.cfg b/src/cfg/ogagent.cfg index a3faa82..da0048e 100644 --- a/src/cfg/ogagent.cfg +++ b/src/cfg/ogagent.cfg @@ -17,6 +17,12 @@ level=full # Log Level, if omitted, will be set to INFO log=DEBUG +# TLS +ca=C:\Program Files (x86)\OGagent\ca.crt +crt=C:\Program Files (x86)\OGagent\ogagent.crt +key=C:\Program Files (x86)\OGagent\ogagent.key + + # Module specific # The sections must match the module name # This section will be passes on activation to module @@ -28,3 +34,8 @@ log=DEBUG pathinterface=/opt/opengnsys/interfaceAdm urlMenu={}://{}/menu-browser urlMsg=http://localhost/cgi-bin/httpd-log.sh + +# TLS +ca=/opt/opengnsys/etc/ca.crt +crt=/opt/opengnsys/etc/ogagent.crt +key=/opt/opengnsys/etc/ogagent.key diff --git a/src/opengnsys/RESTApi.py b/src/opengnsys/RESTApi.py index a59630e..de0c5cf 100644 --- a/src/opengnsys/RESTApi.py +++ b/src/opengnsys/RESTApi.py @@ -34,6 +34,7 @@ +import os import requests import logging import json @@ -43,7 +44,6 @@ from .log import logger from .utils import exceptionToMessage -VERIFY_CERT = False # Do not check server certificate TIMEOUT = 5 # Connection timout, in seconds @@ -88,7 +88,7 @@ class REST(object): the deserialized JSON result or raises an exception in case of error """ - def __init__(self, url): + def __init__(self, url, ca_file=None, crt_file=None, key_file=None): """ Initializes the REST helper url is the full url of the REST API Base, as for example "https://example.com/rest/v1". @@ -105,6 +105,26 @@ class REST(object): except Exception: self.newerRequestLib = False # I no version, guess this must be an old requests + if not self.newerRequestLib: + logger.debug ('TLS not available: python requests library is old') + + self.use_tls = url.startswith ('https') + if self.use_tls: + if not ca_file or not crt_file or not key_file: + raise Exception ('missing TLS parameters in REST constructor') + + errs = 0 + for f in [ca_file, crt_file, key_file]: + if not os.path.exists (f): + logger.error (f'{f}: No such file or directory') + errs += 1 + if errs: + raise Exception ('TLS files not found') + + self.ca_file = ca_file + self.crt_file = crt_file + self.key_file = key_file + # Disable logging requests messages except for errors, ... logging.getLogger("requests").setLevel(logging.CRITICAL) # Tries to disable all warnings @@ -135,14 +155,19 @@ class REST(object): logger.debug('Requesting using GET (no data provided) {}'.format(url)) # Old requests version does not support verify, but it do not checks ssl certificate by default if self.newerRequestLib: - r = requests.get(url, verify=VERIFY_CERT, timeout=TIMEOUT) + if self.use_tls: + r = requests.get(url, cert=(self.crt_file, self.key_file), verify=self.ca_file, timeout=TIMEOUT) + else: + r = requests.get(url, timeout=TIMEOUT) else: r = requests.get(url) else: # POST logger.debug('Requesting using POST {}, data: {}'.format(url, data)) if self.newerRequestLib: - r = requests.post(url, data=data, headers={'content-type': 'application/json'}, - verify=VERIFY_CERT, timeout=TIMEOUT) + if self.use_tls: + r = requests.post(url, data=data, headers={'content-type': 'application/json'}, cert=(self.crt_file, self.key_file), verify=self.ca_file, timeout=TIMEOUT) + else: + r = requests.post(url, data=data, headers={'content-type': 'application/json'}, timeout=TIMEOUT) else: r = requests.post(url, data=data, headers={'content-type': 'application/json'}) diff --git a/src/opengnsys/modules/server/OpenGnSys/__init__.py b/src/opengnsys/modules/server/OpenGnSys/__init__.py index ade870f..8a17cbe 100644 --- a/src/opengnsys/modules/server/OpenGnSys/__init__.py +++ b/src/opengnsys/modules/server/OpenGnSys/__init__.py @@ -130,11 +130,14 @@ class OpenGnSysWorker(ServerWorker): self.random = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(self.length)) # Ensure cfg has required configuration variables or an exception will be thrown try: - url = self.service.config.get(self.name, 'remote') + url = self.service.config.get(self.name, 'remote') + ca_file = self.service.config.get(self.name, 'ca') + crt_file = self.service.config.get(self.name, 'crt') + key_file = self.service.config.get(self.name, 'key') except NoOptionError as e: logger.error("Configuration error: {}".format(e)) raise e - self.REST = REST(url) + self.REST = REST (url, ca_file=ca_file, crt_file=crt_file, key_file=key_file) # Execution level ('full' by default) try: self.exec_level = self.service.config.get(self.name, 'level') @@ -174,7 +177,7 @@ class OpenGnSysWorker(ServerWorker): logger.warn (str (e)) # Trying to initialize on alternative server, if defined # (used in "exam mode" from the University of Seville) - self.REST = REST(self.service.config.get(self.name, 'altremote')) + self.REST = REST(self.service.config.get(self.name, 'altremote'), ca_file=ca_file, crt_file=crt_file, key_file=key_file) self.REST.sendMessage('ogagent/started', {'mac': self.interface.mac, 'ip': self.interface.ip, 'secret': self.random, 'ostype': operations.os_type, 'osversion': operations.os_version, 'alt_url': True, diff --git a/src/opengnsys/workers/oglive_worker.py b/src/opengnsys/workers/oglive_worker.py index 78a7883..f2f25f1 100644 --- a/src/opengnsys/workers/oglive_worker.py +++ b/src/opengnsys/workers/oglive_worker.py @@ -449,13 +449,16 @@ class ogLiveWorker(ServerWorker): self.urlMenu = self.service.config.get (self.name, 'urlMenu') self.urlMsg = self.service.config.get (self.name, 'urlMsg') + ca_file = self.service.config.get (self.name, 'ca') + crt_file = self.service.config.get (self.name, 'crt') + key_file = self.service.config.get (self.name, 'key') url = url.format (ogcore_scheme, ogcore_ip_port) self.urlMenu = self.urlMenu.format (urlmenu_scheme, urlmenu_ip_port) except NoOptionError as e: logger.error ("Configuration error: {}".format (e)) raise e logger.setLevel (loglevel) - self.REST = REST (url) + self.REST = REST (url, ca_file=ca_file, crt_file=crt_file, key_file=key_file) if not self.tomaIPlocal(): raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo')