beremiz

Parents e1562517ccbc
Children 67eead640d01
WAMP: fix IDE/CLI client TLS support, fix runtime client connection, add minimal certificate manager.

- IDE/CLI now takes certificate from cert directory in project, using hostname.cert as file name
- runtime connects from reactor thread instead of main thread (non thread safe, leading to systematic reconnect at start)
- certificate manager only supports returning certificate path based on hostname for now
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/CertManagement.py Tue Mar 04 14:00:41 2025 +0100
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# See COPYING file for copyrights details.
+
+import os
+
+def GetCertPath(project_path, CN):
+ # find Certificate from project
+ crtpath = os.path.join(project_path, 'cert', CN + '.crt')
+ if not os.path.exists(crtpath):
+ raise ValueError(
+ 'Error: Cert %s is missing!\n' % crtpath)
+ return crtpath
+
--- a/connectors/WAMP/__init__.py Sat Mar 01 12:30:04 2025 +0100
+++ b/connectors/WAMP/__init__.py Tue Mar 04 14:00:41 2025 +0100
@@ -26,6 +26,7 @@
from __future__ import absolute_import
from __future__ import print_function
import sys
+import os
import traceback
from functools import partial
from threading import Thread, Event
@@ -33,14 +34,17 @@
from twisted.internet import reactor, threads
from twisted.internet._sslverify import OpenSSLCertificateAuthorities
+from twisted.internet.ssl import optionsForClientTLS
from autobahn.twisted import wamp
from autobahn.twisted.websocket import WampWebSocketClientFactory, connectWS
from autobahn.wamp import types, auth
from autobahn.wamp.exception import TransportLost
from autobahn.wamp.serializer import MsgPackSerializer
+from OpenSSL import crypto
from connectors.ConnectorBase import ConnectorBase
import PSKManagement as PSK
+import CertManagement as Cert
_WampSession = None
_WampConnection = None
@@ -106,11 +110,10 @@
urlprefix = {"WAMP": "ws",
"WAMPS": "wss"}[scheme]
url = urlprefix+"://"+urlpath
-
+ CN = urlpath.split("/")[0].split(":")[0]
try:
secret = PSK.GetSecret(confnodesroot.ProjectPath, ID)
- # TODO: add x509 certificate management together with PSK management.
- trust_store = None
+ trust_store = Cert.GetCertPath(confnodesroot.ProjectPath, CN)
except Exception as e:
confnodesroot.logger.write_error(
_("Connection to {loc} failed with exception {ex}\n").format(
@@ -141,9 +144,16 @@
contextFactory=None
if transport_factory.isSecure:
- contextFactory = MakeSecureContextFactory(
- verifyHostname=True,
- trust_store=trust_store)
+ trustRoot=None
+ if trust_store:
+ if not os.path.exists(trust_store):
+ raise Exception("Wamp trust store not found")
+ cert = crypto.load_certificate(
+ crypto.FILETYPE_PEM,
+ open(trust_store, 'rb').read()
+ )
+ trustRoot=OpenSSLCertificateAuthorities([cert])
+ contextFactory = optionsForClientTLS(transport_factory.host, trustRoot=trustRoot)
# start the client from a Twisted endpoint
conn = connectWS(transport_factory, contextFactory)
--- a/runtime/WampClient.py Sat Mar 01 12:30:04 2025 +0100
+++ b/runtime/WampClient.py Tue Mar 04 14:00:41 2025 +0100
@@ -37,7 +37,7 @@
from autobahn.wamp.serializer import MsgPackSerializer
from twisted.internet.protocol import ReconnectingClientFactory
from twisted.python.components import registerAdapter
-from twisted.internet.ssl import optionsForClientTLS, CertificateOptions
+from twisted.internet.ssl import optionsForClientTLS
from twisted.internet._sslverify import OpenSSLCertificateAuthorities
from OpenSSL import crypto
@@ -55,6 +55,7 @@
# Find pre-existing project WAMP config file
_WampConf = None
+_WampSercretFile = None
_WampSecret = None
_WampTrust = None
@@ -147,7 +148,7 @@
registerOptions = types.RegisterOptions(**kwargs)
except TypeError as e:
registerOptions = None
- print(_("TypeError register option: {}".format(e)))
+ print("TypeError register option: {}".format(e))
self.register(GetCallee(name), u'.'.join((ID, name)), registerOptions)
@@ -157,7 +158,7 @@
for func in DoOnJoin:
func(self)
- print(_('WAMP session joined (%s) by:' % time.ctime()), ID)
+ print('WAMP session joined (%s) by: %s' % (time.ctime(), ID))
def onLeave(self, details):
global _WampSession, _transportFactory
@@ -189,7 +190,7 @@
self.setProtocolOptions(**protocolOptions)
_transportFactory = self
except Exception as e:
- print(_("Custom protocol options failed :"), e)
+ print("Custom protocol options failed :", e)
_transportFactory = None
def setClientFactoryOptions(self, options):
@@ -202,13 +203,13 @@
return ReconnectingClientFactory.buildProtocol(self, addr)
def clientConnectionFailed(self, connector, reason):
- print(_("WAMP Client connection failed (%s) .. retrying ..") %
+ print("WAMP Client connection failed (%s) .. retrying .." %
time.ctime())
super(ReconnectingWampWebSocketClientFactory,
self).clientConnectionFailed(connector, reason)
def clientConnectionLost(self, connector, reason):
- print(_("WAMP Client connection lost (%s) .. retrying ..") %
+ print("WAMP Client connection lost (%s) .. retrying .." %
time.ctime())
super(ReconnectingWampWebSocketClientFactory,
self).clientConnectionFailed(connector, reason)
@@ -270,7 +271,7 @@
def LoadWampSecret(secretfname):
WSClientWampSecret = open(secretfname, 'rb').read()
if len(WSClientWampSecret) == 0:
- raise Exception(_("WAMP secret empty"))
+ raise Exception(_("WAMP secret is empty"))
return WSClientWampSecret
@@ -279,7 +280,8 @@
def RegisterWampClient(wampconf=None, wampsecret=None, ConfDir=None, KeyStore=None, servicename=None):
- global _WampConf, _WampSecret, _WampTrust, defaultWampConfig
+ from twisted.internet import reactor
+ global _WampConf, _WampSecret, _WampSercretFile, _WampTrust, defaultWampConfig
if servicename:
defaultWampConfig["ID"] = servicename
@@ -303,32 +305,37 @@
WampClientConf = GetConfiguration()
+ if not WampClientConf["active"]:
+ print("WAMP deactivated in configuration")
+ return
+
# set secret file path only if not already set
- if _WampSecret is None:
+ if _WampSercretFile is None:
# default project's wamp secret also
# has precedance over commandline given
if os.path.exists(_WampSecretDefault):
- _WampSecret = _WampSecretDefault
+ _WampSercretFile = _WampSecretDefault
else:
- _WampSecret = wampsecret
+ _WampSercretFile = wampsecret
- if _WampSecret is not None:
- if _WampSecret == _WampSecretDefault:
+ if _WampSercretFile is not None:
+ if _WampSercretFile == _WampSecretDefault:
# secret from project dir is raw (no ID prefix)
- secret = LoadWampSecret(_WampSecret)
+ _WampSecret = LoadWampSecret(_WampSercretFile)
else:
# secret from command line is formated ID:PSK
# fall back to PSK data (works because wampsecret is PSKpath)
- _ID, secret = getPSKID()
-
- WampClientConf["secret"] = secret
-
+ _ID, _WampSecret = getPSKID()
else:
raise Exception(_("WAMP no secret file given"))
- if not WampClientConf["active"]:
- print(_("WAMP deactivated in configuration"))
- return
+ reactor.callInThread(_RegisterWampClient)
+
+def _RegisterWampClient():
+ global _WampSecret
+ WampClientConf = GetConfiguration()
+
+ WampClientConf["secret"] = _WampSecret
# create a WAMP application session factory
component_config = types.ComponentConfig(
@@ -352,11 +359,9 @@
contextFactory = MakeSecureContextFactory(WampClientConf["verifyHostname"])
connectWS(_transportFactory, contextFactory)
- print(_("WAMP client connecting to :"), WampClientConf["url"])
- return True
+ print("WAMP client connecting to :", WampClientConf["url"])
else:
- print(_("WAMP client can not connect to :"), WampClientConf["url"])
- return False
+ print("WAMP client can not connect to :", WampClientConf["url"])
def MakeSecureContextFactory(verifyHostname):
if not verifyHostname:
@@ -386,7 +391,7 @@
return True
else:
# do connect
- RegisterWampClient()
+ _RegisterWampClient()
return True
@@ -444,7 +449,7 @@
if secretfile_field is not None:
secretfile = getattr(secretfile_field, "file", None)
if secretfile is not None:
- with open(os.path.realpath(_WampSecret), 'w') as destfd:
+ with open(os.path.realpath(_WampSercretFile), 'w') as destfd:
secretfile.seek(0)
shutil.copyfileobj(secretfile,destfd)
@@ -578,7 +583,7 @@
# TODO: make beautifull message in case of exception
# while loading secret (if empty or dont exist)
- secret = LoadWampSecret(_WampSecret)
+ secret = LoadWampSecret(_WampSercretFile)
return static.Data(secret, 'application/octet-stream'), ()
def deleteTrustStore(ctx, segments):