diff --git a/CHANGELOG.md b/CHANGELOG.md index 7da5077..9ff5979 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.7.0] - 2025-05-27 + +### Changed + +- Use TLS again + ## [5.6.0] - 2025-05-21 ### Changed diff --git a/linux/debian/changelog b/linux/debian/changelog index 6a85cf3..8bf0185 100644 --- a/linux/debian/changelog +++ b/linux/debian/changelog @@ -1,3 +1,9 @@ +ogagent (5.7.0-1) stable; urgency=medium + + * Use TLS again + + -- OpenGnsys developers Wed, 21 May 2025 17:39:13 +0200 + ogagent (5.6.0-1) stable; urgency=medium * Execute 'launch_browser' rather than 'browser' diff --git a/src/VERSION b/src/VERSION index 1bc788d..42cdd0b 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -5.6.0 +5.7.0 diff --git a/src/cfg/ogagent.cfg b/src/cfg/ogagent.cfg index 09c339f..02ca7ca 100644 --- a/src/cfg/ogagent.cfg +++ b/src/cfg/ogagent.cfg @@ -20,9 +20,11 @@ log=DEBUG imgname= # 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 +# 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 diff --git a/src/opengnsys/RESTApi.py b/src/opengnsys/RESTApi.py index 7ef8ce8..541ed49 100644 --- a/src/opengnsys/RESTApi.py +++ b/src/opengnsys/RESTApi.py @@ -96,6 +96,7 @@ class REST(object): @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 += '/' @@ -114,18 +115,43 @@ class REST(object): 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') + 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 - self.ca_file = ca_file - self.crt_file = crt_file - self.key_file = key_file - self.verify_tls = VERIFY_TLS + 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) @@ -159,12 +185,10 @@ class REST(object): if self.newerRequestLib: if self.use_tls: if self.verify_tls: - logger.debug ('nati: using TLS for GET') - v = self.ca_file + 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') - v = False - r = requests.get(url, cert=(self.crt_file, self.key_file), verify=v, timeout=TIMEOUT) + r = requests.get(url, verify=False, timeout=TIMEOUT) else: r = requests.get(url, timeout=TIMEOUT) else: @@ -174,12 +198,10 @@ class REST(object): if self.newerRequestLib: if self.use_tls: if self.verify_tls: - logger.debug ('nati: using TLS for POST') - v = self.ca_file + 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') - v = False - r = requests.post(url, data=data, headers={'content-type': 'application/json'}, cert=(self.crt_file, self.key_file), verify=v, timeout=TIMEOUT) + 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: diff --git a/src/opengnsys/certs.py b/src/opengnsys/certs.py new file mode 100644 index 0000000..e4c070e --- /dev/null +++ b/src/opengnsys/certs.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2014 Virtual Cable S.L. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Virtual Cable S.L. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +''' +@author: Adolfo Gómez, dkmaster at dkmon dot com +''' + +from tempfile import gettempdir +from os.path import exists, join + +CERTFILE = 'OGAgent.pem' + + +def createSelfSignedCert(force=False): + + certFile = join(gettempdir(), CERTFILE) + + if exists(certFile) and not force: + return certFile + + certData = '''-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCb50K3mIznNklz +yVAD7xSQOSJQ6+NPXj7U9/4zLZ+TvmbQ7RqUUsxbfxHbeRnoYTWV2nKk4+tHqmvz +ujLSS/loFhTSMqtrLn7rowSYJoQhKOUkAiQlWkqCfItWgL5pJopDpNHFul9Rn3ds +PMWQTiGeUNR4Y3RnBhr1Q1BsqAzf4m6zFUmgLPPmVLdF4uJ3Tuz8TSy2gWLs5aSr +5do4WamwUfYjRSVMJECmwjUM4rQ8SQgg0sHBeBuDUGNBvBQFac1G7qUcMReeu8Zr +DUtMsXma/l4rA8NB5CRmTrQbTBF4l+jb2BDFebDqDUK1Oqs9X35yOQfDOAFYHiix +PX0IsXOZAgMBAAECggEBAJi3000RrIUZUp6Ph0gzPMuCjDEEwWiQA7CPNX1gpb8O +dp0WhkDhUroWIaICYPSXtOwUTtVjRqivMoxPy1Thg3EIoGC/rdeSdlXRHMEGicwJ +yVyalFnatr5Xzg5wkxVh4XMd0zeDt7e3JD7s0QLo5lm1CEzd77qz6lhzFic5/1KX +bzdULtTlq60dazg2hEbcS4OmM1UMCtRVDAsOIUIZPL0M9j1C1d1iEdYnh2xshKeG +/GOfo95xsgdMlGjtv3hUT5ryKVoEsu+36rGb4VfhPfUvvoVbRx5QZpW+QvxaYh5E +Fi0JEROozFwG31Y++8El7J3yQko8cFBa1lYYUwwpNAECgYEAykT+GiM2YxJ4uVF1 +OoKiE9BD53i0IG5j87lGPnWqzEwYBwnqjEKDTou+uzMGz3MDV56UEFNho7wUWh28 +LpEkjJB9QgbsugjxIBr4JoL/rYk036e/6+U8I95lvYWrzb+rBMIkRDYI7kbQD/mQ +piYUpuCkTymNAu2RisK6bBzJslkCgYEAxVE23OQvkCeOV8hJNPZGpJ1mDS+TiOow +oOScMZmZpail181eYbAfMsCr7ri812lSj98NvA2GNVLpddil6LtS1cQ5p36lFBtV +xQUMZiFz4qVbEak+izL+vPaev/mXXsOcibAIQ+qI/0txFpNhJjpaaSy6vRCBYFmc +8pgSoBnBI0ECgYAUKCn2atnpp5aWSTLYgNosBU4vDA1PShD14dnJMaqyr0aZtPhF +v/8b3btFJoGgPMLxgWEZ+2U4ju6sSFhPf7FXvLJu2QfQRkHZRDbEh7t5DLpTK4Fp +va9vl6Ml7uM/HsGpOLuqfIQJUs87OFCc7iCSvMJDDU37I7ekT2GKkpfbCQKBgBrE +0NeY0WcSJrp7/oqD2sOcYurpCG/rrZs2SIZmGzUhMxaa0vIXzbO59dlWELB8pmnE +Tf20K//x9qA5OxDe0PcVPukdQlH+/1zSOYNliG44FqnHtyd1TJ/gKVtMBiAiE4uO +aSClod5Yosf4SJbCFd/s5Iyfv52NqsAyp1w3Aj/BAoGAVCnEiGUfyHlIR+UH4zZW +GXJMeqdZLfcEIszMxLePkml4gUQhoq9oIs/Kw+L1DDxUwzkXN4BNTlFbOSu9gzK1 +dhuIUGfS6RPL88U+ivC3A0y2jT43oUMqe3hiRt360UQ1GXzp2dMnR9odSRB1wHoO +IOjEBZ8341/c9ZHc5PCGAG8= +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIJAIrEIthCfxUCMA0GCSqGSIb3DQEBCwUAMIGNMQswCQYD +VQQGEwJFUzEPMA0GA1UECAwGTWFkcmlkMREwDwYDVQQHDAhBbGNvcmNvbjEMMAoG +A1UECgwDVURTMQ4wDAYDVQQLDAVBY3RvcjESMBAGA1UEAwwJVURTIEFjdG9yMSgw +JgYJKoZIhvcNAQkBFhlzdXBwb3J0QHVkc2VudGVycHJpc2UuY29tMB4XDTE0MTAy +NjIzNDEyNFoXDTI0MTAyMzIzNDEyNFowgY0xCzAJBgNVBAYTAkVTMQ8wDQYDVQQI +DAZNYWRyaWQxETAPBgNVBAcMCEFsY29yY29uMQwwCgYDVQQKDANVRFMxDjAMBgNV +BAsMBUFjdG9yMRIwEAYDVQQDDAlVRFMgQWN0b3IxKDAmBgkqhkiG9w0BCQEWGXN1 +cHBvcnRAdWRzZW50ZXJwcmlzZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQCb50K3mIznNklzyVAD7xSQOSJQ6+NPXj7U9/4zLZ+TvmbQ7RqUUsxb +fxHbeRnoYTWV2nKk4+tHqmvzujLSS/loFhTSMqtrLn7rowSYJoQhKOUkAiQlWkqC +fItWgL5pJopDpNHFul9Rn3dsPMWQTiGeUNR4Y3RnBhr1Q1BsqAzf4m6zFUmgLPPm +VLdF4uJ3Tuz8TSy2gWLs5aSr5do4WamwUfYjRSVMJECmwjUM4rQ8SQgg0sHBeBuD +UGNBvBQFac1G7qUcMReeu8ZrDUtMsXma/l4rA8NB5CRmTrQbTBF4l+jb2BDFebDq +DUK1Oqs9X35yOQfDOAFYHiixPX0IsXOZAgMBAAGjUDBOMB0GA1UdDgQWBBRShS90 +5lJTNvYPIEqP3GxWwG5iiDAfBgNVHSMEGDAWgBRShS905lJTNvYPIEqP3GxWwG5i +iDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAU0Sp4gXhQmRVzq+7+ +vRFUkQuPj4Ga/d9r5Wrbg3hck3+5pwe9/7APoq0P/M0DBhQpiJKjrD6ydUevC+Y/ +43ZOJPhMlNw0o6TdQxOkX6FDwQanLLs7sfvJvqtVzYn3nuRFKT3dvl7Zg44QMw2M +ay42q59fAcpB4LaDx/i7gOYSS5eca3lYW7j7YSr/+ozXK2KlgUkuCUHN95lOq+dF +trmV9mjzM4CNPZqKSE7kpHRywgrXGPCO000NvEGSYf82AtgRSFKiU8NWLQSEPdcB +k//2dsQZw2cRZ8DrC2B6Tb3M+3+CA6wVyqfqZh1SZva3LfGvq/C+u+ItguzPqNpI +xtvM +-----END CERTIFICATE-----''' + with open(certFile, "wt") as f: + f.write(certData) + + return certFile diff --git a/src/opengnsys/httpserver.py b/src/opengnsys/httpserver.py index de561c1..7ca3ba9 100644 --- a/src/opengnsys/httpserver.py +++ b/src/opengnsys/httpserver.py @@ -40,6 +40,7 @@ from six.moves.BaseHTTPServer import HTTPServer # @UnresolvedImport from six.moves.urllib.parse import unquote # @UnresolvedImport from .utils import exceptionToMessage +from .certs import createSelfSignedCert from .log import logger VERIFY_TLS=True @@ -153,13 +154,34 @@ 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.server = HTTPThreadingServer(address, HTTPServerHandler) context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) - 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') + + 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 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 @@ -168,8 +190,8 @@ class HTTPServerThread(threading.Thread): context.verify_flags &= ~ssl.VERIFY_X509_STRICT s = context.cert_store_stats() - if 'x509_ca' in s: logger.debug (f'{s['x509_ca']} CAs loaded') - if 'x509' in s: logger.debug (f'{s['x509']} certs loaded') + 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)) diff --git a/src/opengnsys/workers/oglive_worker.py b/src/opengnsys/workers/oglive_worker.py index 64e7bf0..741a1f1 100644 --- a/src/opengnsys/workers/oglive_worker.py +++ b/src/opengnsys/workers/oglive_worker.py @@ -33,7 +33,8 @@ import os import re import time -import dbus +try: import dbus ## don't fail on windows (the worker will later refuse to load anyway) +except: pass import random import subprocess import threading