Compare commits

..

59 Commits

Author SHA1 Message Date
Natalia Serrano 10349a35f1 Merge pull request 'refs #2217 merge oggit' (#40) from oggit-debian-changelog into main
Reviewed-on: #40
2025-06-16 11:13:42 +02:00
Natalia Serrano a84bed3439 refs #2217 merge oggit 2025-06-16 11:13:22 +02:00
Natalia Serrano 05d2375e21 Merge pull request 'refs #2217 merge oggit' (#39) from oggit-changelog into main
Reviewed-on: #39
2025-06-16 11:11:09 +02:00
Natalia Serrano 8fea0737f2 refs #2217 merge oggit 2025-06-16 11:10:44 +02:00
Natalia Serrano 7128f12049 refs #2217 Merge branch 'oggit' 2025-06-16 09:55:22 +02:00
Natalia Serrano 734df2749b refs #2217 merge oggit 2025-06-16 09:53:46 +02:00
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
Natalia Serrano 05e0b83b91 Merge pull request 'no-tls' (#34) from no-tls into main
Reviewed-on: #34
2025-05-19 10:52:49 +02:00
Natalia Serrano 9d0589547f refs #2034 disable TLS on request 2025-05-19 10:49:14 +02:00
Natalia Serrano ccb6cdfa54 refs #2034 disable TLS on request 2025-05-19 09:47:07 +02:00
Natalia Serrano 0d375e9aae refs #2034 disable TLS on request 2025-05-19 09:45:43 +02:00
Natalia Serrano 60cc47dd30 Merge pull request 'refs #1998 run the new browser' (#33) from exec-ogbrowser into main
Reviewed-on: #33
2025-05-16 14:53:20 +02:00
Natalia Serrano acc9c2ed07 refs #1998 run the new browser 2025-05-16 14:49:35 +02:00
Vadim vtroshchinskiy 17ec13d77c Soporte de oggit 2025-05-16 09:57:16 +02:00
Natalia Serrano dd445cee65 Merge pull request 'refs #1983 log length of sessions' (#31) from log-sess-len into main
Reviewed-on: #31
2025-05-14 10:46:46 +02:00
Natalia Serrano d231816c0f refs #1983 log length of sessions 2025-05-14 10:45:53 +02:00
Natalia Serrano 0099a05921 Merge pull request 'refs #1952 fix url' (#30) from fix-url into main
Reviewed-on: #30
2025-05-06 13:33:52 +02:00
Natalia Serrano db9f4992be refs #1952 fix url 2025-05-06 13:33:21 +02:00
Natalia Serrano a13765a46e Merge pull request 'add helper script for logging out from windows' (#29) from sched-task into main
Reviewed-on: #29
2025-05-06 13:22:05 +02:00
Natalia Serrano 8431c60a78 refs #1948 add windows logout helper script 2025-05-06 13:20:19 +02:00
Natalia Serrano 130e1139c8 refs #1947 add document 2025-05-06 10:34:48 +02:00
Natalia Serrano a13c3e8a44 Merge pull request 'Use tls' (#28) from tls into main
Reviewed-on: #28
2025-05-06 10:32:14 +02:00
Natalia Serrano 747815c50d refs #1936 remove debug code 2025-05-06 10:31:02 +02:00
Natalia Serrano 27a6f2b470 refs #1940 fix path to cert files on windows 2025-05-06 10:22:51 +02:00
Natalia Serrano cc65ece84a refs #1936 authenticate to ogcore and check ogcore cert 2025-04-25 13:50:09 +02:00
Natalia Serrano fdd4e6d2ca refs #1935 look for cert files on startup 2025-04-25 13:22:04 +02:00
Natalia Serrano 16d24f3d96 refs #1943 have REST object support TLS 2025-04-25 13:17:50 +02:00
Natalia Serrano c39b2530f9 Merge pull request 'refs #1848 decorate oglive methods' (#27) from decorare-oglive-methods into main
Reviewed-on: #27
2025-04-24 15:36:03 +02:00
Natalia Serrano 15e11e6ea3 refs #1848 decorate oglive methods 2025-04-24 15:35:07 +02:00
Natalia Serrano ff042ca0d5 Merge pull request 'refs #1834 update changelogs again' (#26) from oglog2 into main
Reviewed-on: #26
2025-04-14 13:59:32 +02:00
Natalia Serrano 4cf3d505de refs #1834 update changelogs again 2025-04-14 13:59:10 +02:00
Natalia Serrano 52297f2815 Merge pull request 'create json log, clean logging up' (#25) from oglog into main
Reviewed-on: #25
2025-04-14 13:57:32 +02:00
Natalia Serrano 4fe87fc926 refs #1834 update changelogs 2025-04-14 13:51:27 +02:00
Natalia Serrano a7c5ae4d27 refs #1834 create additional json log 2025-04-14 13:49:43 +02:00
Natalia Serrano 5c2fbc36e3 refs #1886 make log a bit cleaner 2025-04-10 14:55:06 +02:00
Natalia Serrano 61e2061cba Merge pull request 'refs #1853 update debian changelog' (#24) from ping4 into main
Reviewed-on: #24
2025-04-10 11:48:00 +02:00
Natalia Serrano 584abfa44c refs #1853 update debian changelog 2025-04-10 11:38:29 +02:00
Natalia Serrano 6e39e473d2 Merge pull request 'refs #1853 ping ogcore from the OS as well' (#23) from ping3 into main
Reviewed-on: #23
2025-04-10 11:32:17 +02:00
Natalia Serrano 16554e5d93 refs #1853 ping ogcore from the OS as well 2025-04-10 11:31:47 +02:00
Natalia Serrano 0e113ecd49 Merge pull request 'refs #1850 actually send ping' (#22) from ping2 into main
Reviewed-on: #22
2025-04-07 13:33:18 +02:00
Natalia Serrano 70517e7ada refs #1850 actually send ping 2025-04-07 13:32:52 +02:00
Natalia Serrano 886df695dc Merge pull request 'ping1' (#21) from ping1 into main
Reviewed-on: #21
2025-04-07 12:03:46 +02:00
Natalia Serrano 08dce3f2d9 refs #1850 improve changelog message 2025-04-07 12:03:18 +02:00
Natalia Serrano b9ce7b7f25 refs #1850 ping ogcore, send timestamp 2025-04-07 12:01:46 +02:00
Natalia Serrano 4d34c5d274 Merge pull request 'refs #1784 ignore module name in URLs' (#20) from override-module into main
Reviewed-on: #20
2025-03-31 11:55:00 +02:00
27 changed files with 836 additions and 160 deletions

View File

@ -6,6 +6,115 @@ 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.9.0] - 2025-06-16
### Added
- Added changes for oggit
## [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
### Changed
- Disabled TLS on request
## [5.3.0] - 2025-05-16
### Changed
- Execute 'launch_browser' rather than 'browser'
## [5.2.0] - 2025-05-14
### Added
- Log duration of user sessions
## [5.1.1] - 2025-05-06
### Fixed
- Fixed URL for notifying stop to ogcore
## [5.1.0] - 2025-05-06
### Added
- Added powershell helper script for logging out from windows
## [5.0.0] - 2025-05-06
### Added
- Use TLS
## [4.0.0] - 2025-04-24
### Added
- Authn/authz to the oglive agent
## [3.3.0] - 2025-04-14
### Added
- Log stuff to a new json log
## [3.2.0] - 2025-04-10
### Added
- Operating system: periodically ping ogcore
## [3.1.0] - 2025-04-07
### Added
- Oglive: periodically ping ogcore
## [3.0.0] - 2025-03-31
### Changed
- Ignore module provided in the URLs to the API
## [2.0.0] - 2025-03-26
### Changed

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -0,0 +1,47 @@
## Crear tarea programada para matar el agente de Windows al cerrar sesión
1. Abrir el task scheduler y pinchar en Create task:
![image info](./sched01.png)
2. Rellenar el nombre y luego pinchar en Change user or group:
![image info](./sched02.png)
3. Pinchar en Advanced:
![image info](./sched03.png)
4. Pinchar en Find now:
![image info](./sched04.png)
5. Seleccionar Administrator y luego Ok, y luego Ok:
![image info](./sched05.png)
6. De vuelta en la pantalla de crear tarea, ir a la pestaña Triggers y pinchar en New:
![image info](./sched06.png)
7. Seleccionar "On disconnect from user session", seleccionar "Connection from local computer", y luego Ok:
![image info](./sched07.png)
8. Ir a la pestaña "Actions" y pinchar en New:
![image info](./sched08.png)
9. Pinchar en Browse, seleccionar C:\Program Files (x86)\OGAgent\stop-agent.ps1, y luego Ok:
![image info](./sched09.png)
10. Ir a la pestaña Conditions y desmarcar las condiciones relativas a Power:
![image info](./sched10.png)
11. Pinchar Ok y la tarea programada queda creada.
En caso de usar remotepc, hay que repetir todos estos pasos, es decir, crear una
tarea programada nueva, seleccionando "Connection from remote computer" en lugar
de "Connection from local computer".

View File

@ -1,3 +1,106 @@
ogagent (5.9.0-1) stable; urgency=medium
* Add changes for oggit
-- OpenGnsys developers <info@opengnsys.es> Mon, 16 Jun 2025 11:12:55 +0200
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
* Disable TLS on request
-- OpenGnsys developers <info@opengnsys.es> Mon, 19 May 2025 09:46:42 +0200
ogagent (5.3.0-1) stable; urgency=medium
* Execute 'launch_browser' rather than 'browser'
-- OpenGnsys developers <info@opengnsys.es> Wed, 14 May 2025 10:50:15 +0200
ogagent (5.2.0-1) stable; urgency=medium
* Log length of user sessions
-- OpenGnsys developers <info@opengnsys.es> Mon, 12 May 2025 11:38:27 +0200
ogagent (5.1.1-1) stable; urgency=medium
* Fix URL for notifying stop to ogcore
-- OpenGnsys developers <info@opengnsys.es> Tue, 06 May 2025 13:31:48 +0200
ogagent (5.1.0-1) stable; urgency=medium
* Include powershell helper script for logging out of windows
-- OpenGnsys developers <info@opengnsys.es> Tue, 06 May 2025 13:30:59 +0200
ogagent (5.0.0-1) stable; urgency=medium
* Use TLS
-- OpenGnsys developers <info@opengnsys.es> Fri, 25 Apr 2025 13:09:49 +0200
ogagent (4.0.0-1) stable; urgency=medium
* Handle authn/authz in the oglive agent
-- OpenGnsys developers <info@opengnsys.es> Thu, 24 Apr 2025 13:28:57 +0200
ogagent (3.3.0-1) stable; urgency=medium
* Create an additional json log file
-- OpenGnsys developers <info@opengnsys.es> Mon, 14 Apr 2025 13:50:32 +0200
ogagent (3.2.0-1) stable; urgency=medium
* Operating system: periodically ping ogcore
-- OpenGnsys developers <info@opengnsys.es> Thu, 10 Apr 2025 11:37:35 +0200
ogagent (3.1.0-1) stable; urgency=medium
* Oglive: periodically ping ogcore
-- OpenGnsys developers <info@opengnsys.es> Mon, 07 Apr 2025 11:50:05 +0200
ogagent (3.0.0-1) stable; urgency=medium
* Ignore module provided in the URLs to the API

View File

@ -98,4 +98,5 @@ coll = COLLECT(
)
import shutil
shutil.copytree ('cfg', '{}/{}/cfg'.format(DISTPATH, dist_name))
shutil.copytree ('cfg', '{}/{}/cfg'. format(DISTPATH, dist_name))
shutil.copy ('stop-agent.ps1', '{}/{}/stop-agent.ps1'.format(DISTPATH, dist_name))

View File

@ -35,6 +35,8 @@ import json
import sys
import time
import os
import socket
import signal
from PyQt6 import QtCore, QtGui, QtWidgets
from about_dialog_ui import Ui_OGAAboutDialog
@ -328,6 +330,29 @@ if __name__ == '__main__':
trayIcon.quit()
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)
trayIcon.show()

View File

@ -1 +1 @@
3.0.0
5.9.0

View File

@ -17,6 +17,16 @@ level=full
# Log Level, if omitted, will be set to INFO
log=DEBUG
imgname=
# TLS
# The agent will look for these files in /opt/opengnsys/etc, /usr/share/OGAgent,
# windows "Program Files (x86)" and the current working directory
ca=ca.crt
crt=ogagent.crt
key=ogagent.key
# Module specific
# The sections must match the module name
# This section will be passes on activation to module
@ -28,3 +38,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

View File

@ -34,6 +34,7 @@
import os
import requests
import logging
import json
@ -43,8 +44,8 @@ from .log import logger
from .utils import exceptionToMessage
VERIFY_CERT = False # Do not check server certificate
TIMEOUT = 5 # Connection timout, in seconds
VERIFY_TLS=True
class RESTError(Exception):
@ -58,8 +59,12 @@ class ConnectionError(RESTError):
# Disable warnings log messages
try:
import urllib3 # @UnusedImport
requests_log = logging.getLogger ('urllib3')
requests_log.setLevel (logging.INFO)
except Exception:
from requests.packages import urllib3 # @Reimport
requests_log = logging.getLogger ('requests.packages.urllib3')
requests_log.setLevel (logging.INFO)
try:
urllib3.disable_warnings() # @UndefinedVariable
@ -84,13 +89,14 @@ 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".
@param url The url of the REST API Base. The trailing '/' can be included or omitted, as desired.
"""
self.endpoint = url
global VERIFY_TLS
if self.endpoint[-1] != '/':
self.endpoint += '/'
@ -101,6 +107,52 @@ 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')
certs_dirs = ['/opt/opengnsys/etc', '/usr/share/OGAgent']
pf = os.environ.get ('PROGRAMFILES(X86)')
if pf: certs_dirs.append (os.path.join (pf, 'OGAgent'))
certs_dirs.append (os.getcwd())
certs_dir = None
for sp in certs_dirs:
if os.path.exists (sp):
logger.debug (f'Looking for TLS files in ({sp})')
certs_dir = sp
break
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, ...
logging.getLogger("requests").setLevel(logging.CRITICAL)
# Tries to disable all warnings
@ -131,14 +183,27 @@ 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:
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:
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:
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:
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'})
@ -148,7 +213,9 @@ class REST(object):
raise Exception (f'response content-type is not "application/json" but "{ct}"')
r = json.loads(r.content) # Using instead of r.json() to make compatible with old requests lib versions
except requests.exceptions.RequestException as e:
raise ConnectionError(e)
code = e.response.status_code
logger.warning (f'request failed, HTTP code "{code}"')
return None
except Exception as e:
raise ConnectionError(exceptionToMessage(e))
@ -160,13 +227,13 @@ class REST(object):
@param data: if None or omitted, message will be a GET, else it will send a POST
@param processData: if True, data will be serialized to json before sending, else, data will be sent as "raw"
"""
logger.debug('Invoking post message {} with data {}'.format(msg, data))
#logger.debug('Invoking post message {} with data {}'.format(msg, data))
if processData and data is not None:
data = json.dumps(data)
url = self._getUrl(msg)
logger.debug('Requesting {}'.format(url))
#logger.debug('Requesting {}'.format(url))
try:
res = self._request(url, data)

View File

@ -43,6 +43,7 @@ from .utils import exceptionToMessage
from .certs import createSelfSignedCert
from .log import logger
VERIFY_TLS=True
class HTTPServerHandler(BaseHTTPRequestHandler):
service = None
@ -153,15 +154,46 @@ class HTTPThreadingServer(ThreadingMixIn, HTTPServer):
class HTTPServerThread(threading.Thread):
def __init__(self, address, service):
super(self.__class__, self).__init__()
global VERIFY_TLS
HTTPServerHandler.service = service # Keep tracking of service so we can intercact with it
self.certFile = createSelfSignedCert()
self.server = HTTPThreadingServer(address, HTTPServerHandler)
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)
logger.debug('Initialized HTTPS Server thread on {}'.format(address))
def getServerUrl(self):

View File

@ -30,6 +30,7 @@
"""
import os
import json
import queue
import socket
@ -193,6 +194,8 @@ class ClientProcessor(threading.Thread):
logger.error('Invalid message in queue: {}'.format(e))
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:
self.clientSocket.close()
except Exception:

View File

@ -34,6 +34,8 @@ import logging
import os
import tempfile
from ..log_format import JsonFormatter
# Logging levels
OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (10000 * (x + 1) for x in range(6))
@ -48,15 +50,25 @@ class LocalLogger(object):
for logDir in ('/var/log', os.path.expanduser('~'), tempfile.gettempdir()):
try:
fname = os.path.join(logDir, 'opengnsys.log')
logging.basicConfig(
filename=fname,
filemode='a',
format='%(levelname)s %(asctime)s (%(threadName)s) (%(funcName)s) %(message)s',
level=logging.DEBUG
)
self.logger = logging.getLogger('opengnsys')
os.chmod(fname, 0o0600)
fname1 = os.path.join (logDir, 'opengnsys.log')
fmt1 = logging.Formatter (fmt='%(levelname)s %(asctime)s (%(threadName)s) (%(funcName)s) %(message)s')
fh1 = logging.FileHandler (filename=fname1, mode='a')
fh1.setFormatter (fmt1)
fh1.setLevel (logging.DEBUG)
fname2 = os.path.join (logDir, 'opengnsys.json.log')
fmt2 = JsonFormatter ({"timestamp": "asctime", "severity": "levelname", "threadName": "threadName", "function": "funcName", "message": "message"}, time_format='%Y-%m-%d %H:%M:%S', msec_format='')
fh2 = logging.FileHandler (filename=fname2, mode='a')
fh2.setFormatter (fmt2)
fh2.setLevel (logging.DEBUG)
self.logger = logging.getLogger ('opengnsys')
self.logger.setLevel (logging.DEBUG)
self.logger.addHandler (fh1)
self.logger.addHandler (fh2)
os.chmod (fname1, 0o0600)
os.chmod (fname2, 0o0600)
return
except Exception:
pass

View File

@ -0,0 +1,55 @@
import json
import logging
class JsonFormatter(logging.Formatter):
"""
Formatter that outputs JSON strings after parsing the LogRecord.
@param dict fmt_dict: Key: logging format attribute pairs. Defaults to {"message": "message"}.
@param str time_format: time.strftime() format string. Default: "%Y-%m-%dT%H:%M:%S"
@param str msec_format: Microsecond formatting. Appended at the end. Default: "%s.%03dZ"
"""
def __init__(self, fmt_dict: dict = None, time_format: str = "%Y-%m-%dT%H:%M:%S", msec_format: str = "%s.%03dZ"):
self.fmt_dict = fmt_dict if fmt_dict is not None else {"message": "message"}
self.default_time_format = time_format
self.default_msec_format = msec_format
self.datefmt = None
def usesTime(self) -> bool:
"""
Overwritten to look for the attribute in the format dict values instead of the fmt string.
"""
return "asctime" in self.fmt_dict.values()
def formatMessage(self, record) -> dict:
"""
Overwritten to return a dictionary of the relevant LogRecord attributes instead of a string.
KeyError is raised if an unknown attribute is provided in the fmt_dict.
"""
return {fmt_key: record.__dict__[fmt_val] for fmt_key, fmt_val in self.fmt_dict.items()}
def format(self, record) -> str:
"""
Mostly the same as the parent's class method, the difference being that a dict is manipulated and dumped as JSON
instead of a string.
"""
record.message = record.getMessage()
if self.usesTime():
record.asctime = self.formatTime(record, self.datefmt)
message_dict = self.formatMessage(record)
if record.exc_info:
# Cache the traceback text to avoid converting it multiple times
# (it's constant anyway)
if not record.exc_text:
record.exc_text = self.formatException(record.exc_info)
if record.exc_text:
message_dict["exc_info"] = record.exc_text
if record.stack_info:
message_dict["stack_info"] = self.formatStack(record.stack_info)
return json.dumps(message_dict, default=str)

View File

@ -30,7 +30,6 @@
@author: Ramón M. Gómez, ramongomez at us dot es
"""
import base64
import os
import random
@ -102,6 +101,20 @@ class OpenGnSysWorker(ServerWorker):
exec_level = None # Execution level (permitted operations)
jobmgr = JobMgr()
## pings ogcore
def mon (self):
n = 0
while True:
time.sleep (1)
n += 1
if not n % 10:
body = {
"iph": self.interface.ip,
"timestamp": int (time.time()),
}
logger.debug (f'about to send ping ({body})')
self.REST.sendMessage ('clients/status/webhook', body)
def onActivation(self):
"""
Sends OGAgent activation notification to OpenGnsys server
@ -117,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')
@ -131,7 +147,11 @@ class OpenGnSysWorker(ServerWorker):
for t in range(0, 300):
try:
# 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:
# Wait 1 sec. and retry
logger.warn (e)
@ -161,7 +181,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,
@ -189,12 +209,18 @@ class OpenGnSysWorker(ServerWorker):
if os.path.isfile(new_hosts_file):
shutil.copyfile(new_hosts_file, hosts_file)
threading.Thread (name='monitoring_thread', target=self.mon, daemon=True).start()
logger.debug ('onActivation ok')
def onDeactivation(self):
"""
Sends OGAgent stopping notification to OpenGnsys server
"""
now = time.time()
for elem in self.user:
sess_len = now - elem['login_ts']
logger.debug ('Session of logged in user {} took {} seconds'.format (elem['username'], int (sess_len)))
logger.debug('onDeactivation')
self.REST.sendMessage('ogagent/stopped', {'mac': self.interface.mac, 'ip': self.interface.ip,
'ostype': operations.os_type, 'osversion': operations.os_version})
@ -205,7 +231,7 @@ class OpenGnSysWorker(ServerWorker):
"""
user, language, self.session_type = tuple(data.split(','))
logger.debug('Received login for {0} using {2} with language {1}'.format(user, language, self.session_type))
self.user.append(user)
self.user.append ({'username': user, 'login_ts': time.time() })
self.REST.sendMessage('ogagent/loggedin', {'ip': self.interface.ip, 'user': user, 'language': language,
'session': self.session_type,
'ostype': operations.os_type, 'osversion': operations.os_version})
@ -214,7 +240,11 @@ class OpenGnSysWorker(ServerWorker):
"""
Sends session logout notification to OpenGnsys server
"""
logger.debug('Received logout for {}'.format(user))
sess_len = 0
for elem in self.user:
if user != elem['username']: continue
sess_len = time.time() - elem['login_ts']
logger.debug ('Received logout for {}, session length {} seconds'.format (user, int (sess_len)))
try:
self.user.pop()
except IndexError:
@ -270,7 +300,7 @@ class OpenGnSysWorker(ServerWorker):
if get_params.get('detail', 'false') == 'true':
res.update({'agent_version': VERSION, 'os_version': operations.os_version, 'sys_load': os.getloadavg()})
if res['loggedin']:
res.update({'sessions': len(self.user), 'current_user': self.user[-1]})
res.update({'sessions': len(self.user), 'current_user': self.user[-1]['username']})
except KeyError:
# Unknown operating system
res = {'status': 'UNK'}

View File

@ -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
@ -75,62 +95,8 @@ class ogAdmClientWorker (ogLiveWorker):
Sends OGAgent stopping notification to OpenGnsys server
"""
logger.debug ('onDeactivation')
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)
self.REST.sendMessage ('ogagent/stopped', {'mac': self.mac, 'ip': self.IPlocal, 'idcentro': self.idcentro, 'idaula': self.idaula,
'idordenador': self.idordenador, 'nombreordenador': self.nombreordenador})
@ -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 "<opengnsys.httpserver.HTTPServerHandler object at 0x7fa788cb8fa0>"
## type(post_params) "<class 'dict'>"
return {'debug':'test'}
def do_CrearImagen (self, post_params):
for k in ['dsk', 'par', 'cpt', 'idi', 'nci', 'ipr', 'nfn', 'ids']:
@ -416,6 +360,60 @@ class ogAdmClientWorker (ogLiveWorker):
}
return self.respuestaEjecucionComando (cmd, herror, ids)
def do_CrearImagenGit (self, post_params):
for k in ['dsk', 'par', 'cpt', 'idi', 'nci', 'ipr', 'nfn', 'ids']:
if k not in post_params:
logger.error (f'required parameter ({k}) not in POST params')
return {}
dsk = post_params['dsk'] ## Disco
par = post_params['par'] ## Número de partición
cpt = post_params['cpt'] ## Código de la partición
idi = post_params['idi'] ## Identificador de la imagen
nci = post_params['nci'] ## Nombre canónico de la imagen
ipr = post_params['ipr'] ## Ip del repositorio
nfn = post_params['nfn']
ids = post_params['ids']
tag = post_params['tag'] ## Tag a crear en git una vez hecho el commit
self.muestraMensaje (7)
try:
res = self.InventariandoSoftware (dsk, par, 'InventarioSoftware') ## Crea inventario Software previamente
except:
logger.warning ('Error al ejecutar el comando')
return {}
if res['contents']:
self.muestraMensaje (2)
inv_sft = res['contents']
try:
self.interfaceAdmin (nfn, [dsk, par, nci, ipr, tag])
self.muestraMensaje (9)
herror = 0
except:
logger.warning ('Error al ejecutar el comando')
self.muestraMensaje (10)
herror = 1
else:
logger.warning ('Error al ejecutar el comando')
herror = 1
inv_sft = ''
self.muestraMenu()
cmd = {
'nfn': 'RESPUESTA_CrearImagenGit',
'idi': idi, ## Identificador de la imagen
'dsk': dsk, ## Número de disco
'par': par, ## Número de partición de donde se creó
'cpt': cpt, ## Tipo o código de partición
'ipr': ipr, ## Ip del repositorio donde se alojó
'inv_sft': inv_sft
}
return self.respuestaEjecucionComando (cmd, herror, ids)
def do_RestaurarImagen (self, post_params):
for k in ['dsk', 'par', 'idi', 'ipr', 'nci', 'ifs', 'ptc', 'nfn', 'ids']:
if k not in post_params:
@ -462,6 +460,54 @@ class ogAdmClientWorker (ogLiveWorker):
}
return self.respuestaEjecucionComando (cmd, herror, ids)
def do_RestaurarImagenGit (self, post_params):
for k in ['dsk', 'par', 'idi', 'ipr', 'nci', 'ifs', 'ptc', 'nfn', 'ids', 'ref']:
if k not in post_params:
logger.error (f'required parameter ({k}) not in POST params')
return {}
dsk = post_params['dsk']
par = post_params['par']
idi = post_params['idi']
ipr = post_params['ipr']
nci = post_params['nci']
ifs = post_params['ifs']
ptc = post_params['ptc'] ## Protocolo de clonación: Unicast, Multicast, Torrent
nfn = post_params['nfn']
ids = post_params['ids']
ref = post_params['ref'] ## Referencia de git a restaurar
self.muestraMensaje (3)
try:
## the ptc.split() is useless right now, since interfaceAdmin() does ' '.join(params) in order to spawn a shell
## however we're going to need it in the future (when everything gets translated into python), plus it's harmless now. So let's do it
#self.interfaceAdmin (nfn, [dsk, par, nci, ipr, ptc])
self.interfaceAdmin (nfn, [dsk, par, nci, ipr, ref] + ptc.split())
self.muestraMensaje (11)
herror = 0
except:
logger.warning ('Error al ejecutar el comando')
self.muestraMensaje (12)
herror = 1
cfg = self.LeeConfiguracion()
if not cfg:
logger.warning ('No se ha podido recuperar la configuración de las particiones del disco')
self.muestraMenu()
cmd = {
'nfn': 'RESPUESTA_RestaurarImagenGit',
'idi': idi, ## Identificador de la imagen
'dsk': dsk, ## Número de disco
'par': par, ## Número de partición
'ifs': ifs, ## Identificador del perfil software
'cfg': self.cfg2obj(cfg), ## Configuración de discos
}
return self.respuestaEjecucionComando (cmd, herror, ids)
def do_Configurar (self, post_params):
for k in ['nfn', 'dsk', 'cfg', 'ids']:
if k not in post_params:
@ -478,7 +524,7 @@ class ogAdmClientWorker (ogLiveWorker):
params = []
disk_info = cfg.pop (0)
logger.debug (f'disk_info ({disk_info})')
for k in ['dis', 'tch']:
for k in ['dis']:
params.append (f'{k}={disk_info[k]}')
disk_info_str = '*'.join (params)
@ -755,16 +801,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 "<opengnsys.httpserver.HTTPServerHandler object at 0x7fa788cb8fa0>"
## type(post_params) "<class 'dict'>"
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 +857,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,31 +881,53 @@ 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)))
return self._long_running_job ('CrearImagen', self.do_CrearImagen, args=(post_params,))
def process_CrearImagenGit (self, path, get_params, post_params, server):
logger.debug ('in process_CrearImagenGit, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.debug ('type(post_params) "{}"'.format (type (post_params)))
return self._long_running_job ('CrearImagenGit', self.do_CrearImagenGit, args=(post_params,))
def process_RestaurarImagenGit (self, path, get_params, post_params, server):
logger.debug ('in process_RestaurarImagenGit, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.debug ('type(post_params) "{}"'.format (type (post_params)))
return self._long_running_job ('RestaurarImagenGit', self.do_RestaurarImagenGit, args=(post_params,))
#def process_CrearImagenBasica (self, path, get_params, post_params, server):
# logger.debug ('in process_CrearImagenBasica, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
# logger.warning ('this method has been removed')
@ -828,6 +938,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 +956,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']

View File

@ -30,6 +30,7 @@
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import os
import json
import socket
import time
@ -197,6 +198,16 @@ class CommonService(object):
Invoked to wait a bit
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)
def notifyStop(self):

View File

@ -36,6 +36,8 @@ import logging
import os
import tempfile
from ..log_format import JsonFormatter
# Valid logging levels, from UDS Broker (uds.core.utils.log)
OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (10000 * (x + 1) for x in range(6))
@ -44,13 +46,24 @@ class LocalLogger(object):
def __init__(self):
# tempdir is different for "user application" and "service"
# service wil get c:\windows\temp, while user will get c:\users\XXX\appdata\local\temp
logging.basicConfig(
filename=os.path.join(tempfile.gettempdir(), 'opengnsys.log'),
filemode='a',
format='%(levelname)s %(asctime)s (%(threadName)s) (%(funcName)s) %(message)s',
level=logging.DEBUG
)
fname1 = os.path.join (tempfile.gettempdir(), 'opengnsys.log')
fmt1 = logging.Formatter (fmt='%(levelname)s %(asctime)s (%(threadName)s) (%(funcName)s) %(message)s')
fh1 = logging.FileHandler (filename=fname1, mode='a')
fh1.setFormatter (fmt1)
fh1.setLevel (logging.DEBUG)
fname2 = os.path.join (tempfile.gettempdir(), 'opengnsys.json.log')
fmt2 = JsonFormatter ({"timestamp": "asctime", "severity": "levelname", "threadName": "threadName", "function": "funcName", "message": "message"}, time_format='%Y-%m-%d %H:%M:%S', msec_format='')
fh2 = logging.FileHandler (filename=fname2, mode='a')
fh2.setFormatter (fmt2)
fh2.setLevel (logging.DEBUG)
self.logger = logging.getLogger('opengnsys')
self.logger.setLevel (logging.DEBUG)
self.logger.addHandler (fh1)
self.logger.addHandler (fh2)
self.serviceLogger = False
def log(self, level, message):

View File

@ -33,6 +33,8 @@
import os
import re
import time
try: import dbus ## don't fail on windows (the worker will later refuse to load anyway)
except: pass
import random
import subprocess
import threading
@ -198,17 +200,20 @@ class ogLiveWorker(ServerWorker):
progress = None
for i in ary:
if m := re.search (r'^\[([0-9]+)\]', i): ## look for strings like '[10]', '[60]'
logger.debug (f"matched regex, m.groups ({m.groups()})")
#logger.debug (f"matched regex, m.groups ({m.groups()})")
progress = float (m.groups()[0]) / 100
return progress
## monitors child threads, waits for them to finish
## pings ogcore
def mon (self):
n = 0
while True:
with self.thread_lock:
for k in self.thread_list:
elem = self.thread_list[k]
if 'thread' not in elem: continue
logger.debug (f'considering thread ({k})')
#logger.debug (f'considering thread ({k})')
if self.pid_q:
if not self.pid_q.empty():
@ -235,9 +240,26 @@ class ogLiveWorker(ServerWorker):
self.notifier (k, elem['result'])
time.sleep (1)
n += 1
if not n % 10:
alive_threads = []
for k in self.thread_list:
elem = self.thread_list[k]
if 'thread' not in elem: continue
alive_threads.append (k)
if alive_threads:
s = ','.join (alive_threads)
logger.debug (f'alive threads: {s}')
body = {
'iph': self.IPlocal,
'timestamp': int (time.time()),
}
#logger.debug (f'about to send ping ({body})')
self.REST.sendMessage ('clients/status/webhook', body)
def interfaceAdmin (self, method, parametros=[]):
if method in ['Apagar', 'CambiarAcceso', 'Configurar', 'CrearImagen', 'EjecutarScript', 'getConfiguration', 'getIpAddress', 'IniciarSesion', 'InventarioHardware', 'InventarioSoftware', 'Reiniciar', 'RestaurarImagen']:
if method in ['Apagar', 'CambiarAcceso', 'Configurar', 'CrearImagen', 'CrearImagenGit', 'EjecutarScript', 'getConfiguration', 'getIpAddress', 'IniciarSesion', 'InventarioHardware', 'InventarioSoftware', 'Reiniciar', 'RestaurarImagen', 'RestaurarImagenGit']:
## python
logger.debug (f'({method}) is a python method')
exe = '{}/{}.py'.format (self.pathinterface, method)
@ -266,7 +288,8 @@ class ogLiveWorker(ServerWorker):
self.pid_q.put (p.pid)
else:
## esto sucede por ejemplo cuando arranca el agente, que estamos en interfaceAdmin() en el mismo hilo, sin _long_running_job ni hilo separado
logger.debug ('no queue--not writing any PID to it')
#logger.debug ('no queue--not writing any PID to it')
pass
sout = serr = ''
while p.poll() is None:
@ -282,12 +305,12 @@ class ogLiveWorker(ServerWorker):
serr = serr.strip()
## DEBUG
logger.info (f'stdout follows:')
logger.debug (f'stdout follows:')
for l in sout.splitlines():
logger.info (f' {l}')
logger.info (f'stderr follows:')
for l in serr.splitlines():
logger.info (f' {l}')
logger.debug (f' {l}')
#logger.debug (f'stderr follows:')
#for l in serr.splitlines():
# logger.debug (f' {l}')
## /DEBUG
if 0 != p.returncode:
cmd_txt = ' '.join (proc)
@ -326,9 +349,7 @@ class ogLiveWorker(ServerWorker):
res = self.REST.sendMessage ('/'.join ([self.name, path]), obj)
if (type (res) is not dict):
#logger.error ('No se ha podido establecer conexión con el Servidor de Administración') ## Error de conexión con el servidor
logger.debug (f'res ({res})')
logger.error ('Error al enviar trama ***send() fallo')
logger.error (f'response is not a dict ({res})')
return False
return res
@ -350,17 +371,25 @@ class ogLiveWorker(ServerWorker):
def cargaPaginaWeb (self, url=None):
if (not url): url = self.urlMenu
os.system ('pkill -9 browser')
p = subprocess.Popen (['/usr/bin/browser', '-qws', 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:
p.wait (2) ## if the process dies before 2 seconds...
logger.error ('Error al ejecutar la llamada a la interface de administración')
logger.error ('Error en la creación del proceso hijo')
logger.error ('return code "{}"'.format (p.returncode))
return False
except subprocess.TimeoutExpired:
pass
b.call_blocking (dest, path, interface, method, 's', [url])
except Exception as e:
if 'ServiceUnknown' in str(e):
## browser not running
subprocess.Popen (['/usr/bin/launch_browser', url])
else:
logger.error (f'Error al cambiar URL: ({e})')
return False
return True
@ -377,7 +406,7 @@ class ogLiveWorker(ServerWorker):
logger.error (e)
logger.error ('No se ha podido recuperar la dirección IP del cliente')
return None
logger.debug ('parametroscfg ({})'.format (parametroscfg))
#logger.debug ('parametroscfg ({})'.format (parametroscfg))
return parametroscfg
def cfg2obj (self, cfg):
@ -432,13 +461,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')