Compare commits

...

15 Commits

Author SHA1 Message Date
Natalia Serrano 946020d728 Merge pull request 'refs #2208 improve fail condition when no network is detected' (#38) from error-no-net into main
Reviewed-on: #38
2025-06-13 10:03:54 +02:00
Natalia Serrano 1a38999aef refs #2208 improve fail condition when no network is detected 2025-06-13 10:03:24 +02:00
Natalia Serrano 2dd678737b Merge pull request 'ogagentuser-sigterm' (#37) from ogagentuser-sigterm into main
Reviewed-on: #37
2025-06-12 15:39:24 +02:00
Natalia Serrano 165746a94b refs #2211 send logout upon client disconnect 2025-06-12 15:37:17 +02:00
Natalia Serrano 3553aee8ce refs #2179 check that windows-only variable is defined 2025-06-05 12:10:22 +02:00
Natalia Serrano b77b42ec22 refs #2177 correctly handle UNIX signals 2025-06-05 12:09:07 +02:00
Natalia Serrano e20c671c1e Merge pull request 'tls-again' (#36) from tls-again into main
Reviewed-on: #36
2025-05-28 10:53:07 +02:00
Natalia Serrano 15d4f2cf6b refs #2056 #2057 look for certs in a few places 2025-05-28 10:47:40 +02:00
Natalia Serrano e920a3c681 refs #2055 make TLS checks optional for the server 2025-05-21 17:36:13 +02:00
Natalia Serrano 921706e9f0 refs #2054 use a global to specify TLS verification or not 2025-05-21 17:31:33 +02:00
Natalia Serrano 63944cef0e refs #2054 send client cert to ogcore, optionally verify ogcore's cert 2025-05-21 17:30:11 +02:00
Natalia Serrano 3ae4471d5d refs #2055 use proper server cert, demand client cert 2025-05-21 17:21:47 +02:00
Natalia Serrano d1ce3b5cc9 Merge pull request 'refs #2060 launch QT6 browser, use dbus to change URLs' (#35) from browser-nuevo into main
Reviewed-on: #35
2025-05-21 16:05:40 +02:00
Natalia Serrano 1fa2a4f0bb refs #2060 launch QT6 browser, use dbus to change URLs 2025-05-21 16:02:46 +02:00
Natalia Serrano ac9ab7e8f5 refs #2036 downgrade to qt4 browser again 2025-05-19 11:05:43 +02:00
11 changed files with 231 additions and 34 deletions

View File

@ -6,6 +6,43 @@ 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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [5.8.1] - 2025-06-13
### Fixed
- Improve fail condition when no network is detected
## [5.8.0] - 2025-06-12
### Changed
- Agents aren't being sent any signals on user logout. On the server side, assume that client disconnection == logout
## [5.7.1] - 2025-06-05
### Fixed
- Correcly handle UNIX signals in the user instance of the agent
## [5.7.0] - 2025-05-27
### Changed
- Use TLS again
## [5.6.0] - 2025-05-21
### Changed
- Launch QT6 browser
- Change URLs using dbus
## [5.5.0] - 2025-05-19
### Changed
- Revert to the QT4 browser again
## [5.4.0] - 2025-05-19 ## [5.4.0] - 2025-05-19
### Changed ### Changed

View File

@ -1,3 +1,40 @@
ogagent (5.8.1-1) stable; urgency=medium
* Improve fail condition when no network is detected
-- OpenGnsys developers <info@opengnsys.es> Fri, 13 Jun 2025 10:01:43 +0200
ogagent (5.8.0-1) stable; urgency=medium
* When client disconnect, assume that the user logged out
-- OpenGnsys developers <info@opengnsys.es> Thu, 12 Jun 2025 15:30:50 +0200
ogagent (5.7.1-1) stable; urgency=medium
* Correctly handle UNIX signals
-- OpenGnsys developers <info@opengnsys.es> Thu, 05 Jun 2025 12:07:30 +0200
ogagent (5.7.0-1) stable; urgency=medium
* Use TLS again
-- OpenGnsys developers <info@opengnsys.es> Wed, 21 May 2025 17:39:13 +0200
ogagent (5.6.0-1) stable; urgency=medium
* Execute 'launch_browser' rather than 'browser'
* Change URLs using dbus
-- OpenGnsys developers <info@opengnsys.es> Wed, 21 May 2025 15:06:52 +0200
ogagent (5.5.0-1) stable; urgency=medium
* Return to the QT4 browser again
-- OpenGnsys developers <info@opengnsys.es> Mon, 19 May 2025 10:57:37 +0200
ogagent (5.4.0-1) stable; urgency=medium ogagent (5.4.0-1) stable; urgency=medium
* Disable TLS on request * Disable TLS on request

View File

@ -35,6 +35,8 @@ import json
import sys import sys
import time import time
import os import os
import socket
import signal
from PyQt6 import QtCore, QtGui, QtWidgets from PyQt6 import QtCore, QtGui, QtWidgets
from about_dialog_ui import Ui_OGAAboutDialog from about_dialog_ui import Ui_OGAAboutDialog
@ -328,6 +330,29 @@ if __name__ == '__main__':
trayIcon.quit() trayIcon.quit()
sys.exit(1) sys.exit(1)
## begin SIGTERM handling
signal_socket = socket.socketpair()
signal_socket[0].setblocking(False)
signal_socket[1].setblocking(False)
signal.set_wakeup_fd(signal_socket[0].fileno())
def signal_handler(signum, frame):
#print (f"Received signal {signum}")
pass
def qt_signal_handler():
data = signal_socket[1].recv(1)
#print(f"Signal ({data}) received via socket, shutting down gracefully...")
if trayIcon:
trayIcon.quit()
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
notifier = QtCore.QSocketNotifier(signal_socket[1].fileno(), QtCore.QSocketNotifier.Type.Read)
notifier.activated.connect(qt_signal_handler)
## end SIGTERM handling
app.aboutToQuit.connect(trayIcon.cleanup) app.aboutToQuit.connect(trayIcon.cleanup)
trayIcon.show() trayIcon.show()

View File

@ -1 +1 @@
5.4.0 5.8.1

View File

@ -20,9 +20,11 @@ log=DEBUG
imgname= imgname=
# TLS # TLS
ca=C:\Program Files (x86)\OGagent\ca.crt # The agent will look for these files in /opt/opengnsys/etc, /usr/share/OGAgent,
crt=C:\Program Files (x86)\OGagent\ogagent.crt # windows "Program Files (x86)" and the current working directory
key=C:\Program Files (x86)\OGagent\ogagent.key ca=ca.crt
crt=ogagent.crt
key=ogagent.key
# Module specific # Module specific

View File

@ -45,6 +45,7 @@ from .log import logger
from .utils import exceptionToMessage from .utils import exceptionToMessage
TIMEOUT = 5 # Connection timout, in seconds TIMEOUT = 5 # Connection timout, in seconds
VERIFY_TLS=True
class RESTError(Exception): class RESTError(Exception):
@ -95,6 +96,7 @@ class REST(object):
@param url The url of the REST API Base. The trailing '/' can be included or omitted, as desired. @param url The url of the REST API Base. The trailing '/' can be included or omitted, as desired.
""" """
self.endpoint = url self.endpoint = url
global VERIFY_TLS
if self.endpoint[-1] != '/': if self.endpoint[-1] != '/':
self.endpoint += '/' self.endpoint += '/'
@ -109,21 +111,47 @@ class REST(object):
logger.debug ('TLS not available: python requests library is old') logger.debug ('TLS not available: python requests library is old')
self.use_tls = url.startswith ('https') self.use_tls = url.startswith ('https')
#if self.use_tls: if self.use_tls:
# if not ca_file or not crt_file or not key_file: if not ca_file or not crt_file or not key_file:
# raise Exception ('missing TLS parameters in REST constructor') raise Exception ('missing TLS parameters in REST constructor')
#
# errs = 0 certs_dirs = ['/opt/opengnsys/etc', '/usr/share/OGAgent']
# for f in [ca_file, crt_file, key_file]: pf = os.environ.get ('PROGRAMFILES(X86)')
# if not os.path.exists (f): if pf: certs_dirs.append (os.path.join (pf, 'OGAgent'))
# logger.error (f'{f}: No such file or directory') certs_dirs.append (os.getcwd())
# errs += 1 certs_dir = None
# if errs: for sp in certs_dirs:
# raise Exception ('TLS files not found') if os.path.exists (sp):
# logger.debug (f'Looking for TLS files in ({sp})')
#self.ca_file = ca_file certs_dir = sp
#self.crt_file = crt_file break
#self.key_file = key_file
if not certs_dir:
logger.debug ("Don't know where to look for TLS files")
errs = 1
else:
errs = 0
for f in [ca_file, crt_file, key_file]:
if os.path.exists (f'{certs_dir}/{f}'):
logger.debug (f'{certs_dir}/{f}: found')
else:
logger.error (f'{f}: No such file or directory')
errs += 1
if errs:
self.verify_tls = False
logger.debug ('HTTP client: using insecure TLS to talk to ogcore due to missing files')
else:
self.ca_file = f'{certs_dir}/{ca_file}'
self.crt_file = f'{certs_dir}/{crt_file}'
self.key_file = f'{certs_dir}/{key_file}'
self.verify_tls = VERIFY_TLS
if self.verify_tls:
logger.debug ('HTTP client: using TLS to talk to ogcore')
else:
logger.debug ('HTTP client: using insecure TLS as requested to talk to ogcore')
else:
logger.debug ('HTTP client: not using TLS to talk to ogcore')
# Disable logging requests messages except for errors, ... # Disable logging requests messages except for errors, ...
logging.getLogger("requests").setLevel(logging.CRITICAL) logging.getLogger("requests").setLevel(logging.CRITICAL)
@ -156,7 +184,11 @@ class REST(object):
# Old requests version does not support verify, but it do not checks ssl certificate by default # Old requests version does not support verify, but it do not checks ssl certificate by default
if self.newerRequestLib: if self.newerRequestLib:
if self.use_tls: if self.use_tls:
r = requests.get(url, verify=False, timeout=TIMEOUT) if self.verify_tls:
r = requests.get(url, cert=(self.crt_file, self.key_file), verify=self.ca_file, timeout=TIMEOUT)
else:
logger.warning ('using insecure TLS for GET')
r = requests.get(url, verify=False, timeout=TIMEOUT)
else: else:
r = requests.get(url, timeout=TIMEOUT) r = requests.get(url, timeout=TIMEOUT)
else: else:
@ -165,7 +197,11 @@ class REST(object):
logger.debug('Requesting using POST {}, data: {}'.format(url, data)) logger.debug('Requesting using POST {}, data: {}'.format(url, data))
if self.newerRequestLib: if self.newerRequestLib:
if self.use_tls: if self.use_tls:
r = requests.post(url, data=data, headers={'content-type': 'application/json'}, verify=False, timeout=TIMEOUT) if self.verify_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:
logger.warning ('using insecure TLS for POST')
r = requests.post(url, data=data, headers={'content-type': 'application/json'}, verify=False, timeout=TIMEOUT)
else: else:
r = requests.post(url, data=data, headers={'content-type': 'application/json'}, timeout=TIMEOUT) r = requests.post(url, data=data, headers={'content-type': 'application/json'}, timeout=TIMEOUT)
else: else:

View File

@ -43,6 +43,7 @@ from .utils import exceptionToMessage
from .certs import createSelfSignedCert from .certs import createSelfSignedCert
from .log import logger from .log import logger
VERIFY_TLS=True
class HTTPServerHandler(BaseHTTPRequestHandler): class HTTPServerHandler(BaseHTTPRequestHandler):
service = None service = None
@ -153,15 +154,46 @@ class HTTPThreadingServer(ThreadingMixIn, HTTPServer):
class HTTPServerThread(threading.Thread): class HTTPServerThread(threading.Thread):
def __init__(self, address, service): def __init__(self, address, service):
super(self.__class__, self).__init__() super(self.__class__, self).__init__()
global VERIFY_TLS
HTTPServerHandler.service = service # Keep tracking of service so we can intercact with it HTTPServerHandler.service = service # Keep tracking of service so we can intercact with it
self.certFile = createSelfSignedCert()
self.server = HTTPThreadingServer(address, HTTPServerHandler) self.server = HTTPThreadingServer(address, HTTPServerHandler)
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(certfile=self.certFile)
pf = os.environ.get ('PROGRAMFILES(X86)')
if pf: pf = os.path.join (pf, 'OGAgent')
if os.path.exists ('/opt/opengnsys/etc/ogagent.crt') and os.path.exists ('/opt/opengnsys/etc/ogagent.key') and os.path.exists ('/opt/opengnsys/etc/ca.crt'):
logger.debug ('HTTP server: using certificate/CA from /opt/opengnsys/etc')
context.load_cert_chain (certfile='/opt/opengnsys/etc/ogagent.crt', keyfile='/opt/opengnsys/etc/ogagent.key')
context.load_verify_locations (cafile='/opt/opengnsys/etc/ca.crt')
elif pf and os.path.exists (os.path.join (pf, 'ogagent.crt')) and os.path.exists (os.path.join (pf, 'ogagent.key')) and os.path.exists (os.path.join (pf, 'ca.crt')):
logger.debug (f'HTTP server: using certificate/CA from the installation path ({pf})')
context.load_cert_chain (certfile=os.path.join (pf, 'ogagent.crt'), keyfile=os.path.join (pf, 'ogagent.key'))
context.load_verify_locations (cafile=os.path.join (pf, 'ca.crt'))
elif os.path.exists ('./ogagent.crt') and os.path.exists ('./ogagent.key') and os.path.exists ('./ca.crt'):
cwd = os.getcwd()
logger.debug (f'HTTP server: using certificate/CA from the current working directory ({cwd})')
context.load_cert_chain (certfile=f'{cwd}/ogagent.crt', keyfile=f'{cwd}/ogagent.key')
context.load_verify_locations (cafile=f'{cwd}/ca.crt')
else:
logger.debug ('HTTP server: using a self-signed certificate')
self.certFile = createSelfSignedCert()
context.load_cert_chain (certfile=self.certFile)
VERIFY_TLS = False
if VERIFY_TLS:
context.verify_mode = ssl.CERT_REQUIRED
context.verify_flags &= ssl.VERIFY_X509_STRICT
else:
context.verify_mode = ssl.CERT_NONE
context.verify_flags &= ~ssl.VERIFY_X509_STRICT
s = context.cert_store_stats()
if 'x509_ca' in s: logger.debug (f'HTTP server: {s['x509_ca']} CAs loaded')
if 'x509' in s: logger.debug (f'HTTP server: {s['x509']} certs loaded')
self.server.socket = context.wrap_socket(self.server.socket, server_side=True) self.server.socket = context.wrap_socket(self.server.socket, server_side=True)
logger.debug('Initialized HTTPS Server thread on {}'.format(address)) logger.debug('Initialized HTTPS Server thread on {}'.format(address))
def getServerUrl(self): def getServerUrl(self):

View File

@ -30,6 +30,7 @@
""" """
import os
import json import json
import queue import queue
import socket import socket
@ -193,6 +194,8 @@ class ClientProcessor(threading.Thread):
logger.error('Invalid message in queue: {}'.format(e)) logger.error('Invalid message in queue: {}'.format(e))
logger.debug('Client processor stopped') logger.debug('Client processor stopped')
if os.path.exists ('/windows/temp'): open ('/windows/temp/ogagentuser_died', 'w').close()
else: open ( '/tmp/ogagentuser_died', 'w').close()
try: try:
self.clientSocket.close() self.clientSocket.close()
except Exception: except Exception:

View File

@ -147,7 +147,11 @@ class OpenGnSysWorker(ServerWorker):
for t in range(0, 300): for t in range(0, 300):
try: try:
# Get the first network interface # Get the first network interface
self.interface = list(operations.getNetworkInfo())[0] nets = list (operations.getNetworkInfo())
if 0 == len (nets):
logger.error ('No network interfaces found')
raise Exception ('No network interfaces found')
self.interface = nets[0]
except Exception as e: except Exception as e:
# Wait 1 sec. and retry # Wait 1 sec. and retry
logger.warn (e) logger.warn (e)

View File

@ -30,6 +30,7 @@
@author: Adolfo Gómez, dkmaster at dkmon dot com @author: Adolfo Gómez, dkmaster at dkmon dot com
""" """
import os
import json import json
import socket import socket
import time import time
@ -197,6 +198,16 @@ class CommonService(object):
Invoked to wait a bit Invoked to wait a bit
CAN be OVERRIDDEN CAN be OVERRIDDEN
""" """
client_died=False
if os.path.exists ('/windows/temp/ogagentuser_died'):
os.unlink ('/windows/temp/ogagentuser_died')
client_died=True
elif os.path.exists ('/tmp/ogagentuser_died'):
os.unlink ('/tmp/ogagentuser_died')
client_died=True
if client_died:
self.notifyLogout (b'')
time.sleep(float(miliseconds) / 1000) time.sleep(float(miliseconds) / 1000)
def notifyStop(self): def notifyStop(self):

View File

@ -33,6 +33,8 @@
import os import os
import re import re
import time import time
try: import dbus ## don't fail on windows (the worker will later refuse to load anyway)
except: pass
import random import random
import subprocess import subprocess
import threading import threading
@ -369,17 +371,25 @@ class ogLiveWorker(ServerWorker):
def cargaPaginaWeb (self, url=None): def cargaPaginaWeb (self, url=None):
if (not url): url = self.urlMenu if (not url): url = self.urlMenu
os.system ('pkill -f -9 browser')
os.system ('pkill -f -9 OGBrowser')
os.system ('pkill -f -9 QtWebEngineProcess')
p = subprocess.Popen (['/usr/bin/launch_browser', url]) dbus_address = os.environ.get ('DBUS_SESSION_BUS_ADDRESS')
if not dbus_address: logger.warning ('env var DBUS_SESSION_BUS_ADDRESS not set, cargaPaginaWeb() will likely not work')
b = dbus.SystemBus()
dest = 'es.opengnsys.OGBrowser.browser'
path = '/'
interface = None
method = 'setURL'
signature = 's'
try: try:
p.wait (2) ## if the process dies before 2 seconds... b.call_blocking (dest, path, interface, method, 's', [url])
logger.error ('Error al ejecutar OGBrowser, return code "{}"'.format (p.returncode)) except Exception as e:
return False if 'ServiceUnknown' in str(e):
except subprocess.TimeoutExpired: ## browser not running
pass subprocess.Popen (['/usr/bin/launch_browser', url])
else:
logger.error (f'Error al cambiar URL: ({e})')
return False
return True return True