From ff55b01182f03cb8bbe9865638b2fc0b495914cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Tue, 4 Mar 2008 14:42:51 +0100 Subject: [PATCH] Add ssl support for netrpc and xmlrpc --- doc/SSL.t2t | 12 +++++++ trytond/config.py | 15 ++++++-- trytond/netsvc.py | 87 ++++++++++++++++++++++++++++++--------------- trytond/pysocket.py | 65 +++++++++++++++++++++++++++------ trytond/trytond.py | 2 +- 5 files changed, 140 insertions(+), 41 deletions(-) create mode 100644 doc/SSL.t2t diff --git a/doc/SSL.t2t b/doc/SSL.t2t new file mode 100644 index 0000000000..fd15a16d97 --- /dev/null +++ b/doc/SSL.t2t @@ -0,0 +1,12 @@ + +== Howto activate SSL on trytond == + +- You need to have OpenSSL and pyOpenSSL installed on your system. + +- Generate a self-signed certificate with this command: + + openssl req -new -x509 -keyout server.pem -out server.pem -days 365 -nodes + +- Start the trytond server with those options: + + trytond.py -S --privatekey server.pem --certificate server.pem diff --git a/trytond/config.py b/trytond/config.py index 60cc2bba76..3474773a32 100644 --- a/trytond/config.py +++ b/trytond/config.py @@ -29,6 +29,8 @@ class ConfigManager(object): 'pidfile': None, 'logfile': None, 'secure': False, + 'privatekey': '/etc/ssl/trytond/server.key', + 'certificate': '/etc/ssl/trytond/server.pem', 'smtp_server': 'localhost', 'smtp_user': False, 'smtp_password': False, @@ -59,8 +61,6 @@ class ConfigManager(object): help="stop the server after it initializes") group.add_option("-n", "--interface", dest="interface", help="specify the TCP IP address") - group.add_option("-S", "--secure", dest="secure", action="store_true", - help="launch server over https instead of http") group.add_option("--no-netrpc", dest="netrpc", action="store_false", help="disable netrpc") group.add_option("-p", "--net-port", dest="netport", @@ -75,6 +75,15 @@ class ConfigManager(object): help="specify the TCP port for webdav") parser.add_option_group(group) + group = optparse.OptionGroup(parser, "SSL options") + group.add_option("-S", "--secure", dest="secure", action="store_true", + help="launch server over SSL") + group.add_option("--privatekey", dest="privatekey", + help="specify the file for the private key") + group.add_option("--certificate", dest="certificate", + help="specify the file for the certificate") + parser.add_option_group(group) + group = optparse.OptionGroup(parser, "Modules related options") group.add_option("-i", "--init", dest="init", help="init a module (use \"all\" for all modules)") @@ -149,6 +158,8 @@ class ConfigManager(object): 'logfile', 'pidfile', 'secure', + 'privatekey', + 'certificate', 'smtp_server', 'smtp_user', 'smtp_password', diff --git a/trytond/netsvc.py b/trytond/netsvc.py index c2cf50e017..87ae608dea 100644 --- a/trytond/netsvc.py +++ b/trytond/netsvc.py @@ -250,6 +250,14 @@ class SimpleXMLRPCRequestHandler(GenericXMLRPCRequestHandler, ) +class SecureXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): + + def setup(self): + self.connection = self.request + self.rfile = socket.fileobject(self.request, "rb", self.rbufsize) + self.wfile = socket._fileobject(self.request, "wb", self.wbufsize) + + class SimpleThreadedXMLRPCServer(SocketServer.ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer): @@ -263,6 +271,25 @@ class SimpleThreadedXMLRPCServer6(SimpleThreadedXMLRPCServer): address_family = socket.AF_INET6 +class SecureThreadedXMLRPCServer(SimpleThreadedXMLRPCServer): + + def __init__(self, server_address, HandlerClass, logRequests=1): + from OpenSSL import SSL + SimpleThreadedXMLRPCServer.__init__(self, server_address, HandlerClass, + logRequests) + ctx = SSL.Context(SSL.SSLv23_METHOD) + ctx.use_privatekey_file(CONFIG['privatekey']) + ctx.use_certificate_file(CONFIG['certificate']) + self.socket = SSL.Connection(ctx, socket.socket(self.address_family, + self.socket_type)) + self.server_bind() + self.server_activate() + + +class SecureThreadedXMLRPCServer6(SecureThreadedXMLRPCServer): + address_family = socket.AF_INET6 + + class HttpDaemon(threading.Thread): def __init__(self, interface, port, secure=False): @@ -270,27 +297,19 @@ class HttpDaemon(threading.Thread): self.secure = secure self.running = False if secure: -# from ssl import SecureXMLRPCServer -# class SecureXMLRPCRequestHandler(GenericXMLRPCRequestHandler, -# SecureXMLRPCServer.SecureXMLRPCRequestHandler): -# SecureXMLRPCServer.SecureXMLRPCRequestHandler.rpc_paths = ( -# '/xmlrpc/db', -# '/xmlrpc/common', -# '/xmlrpc/object', -# '/xmlrpc/report', -# '/xmlrpc/wizard', -# ) -# class SecureThreadedXMLRPCServer(SocketServer.ThreadingMixIn, -# SecureXMLRPCServer.SecureXMLRPCServer): -# -# def server_bind(self): -# self.socket.setsockopt(socket.SOL_SOCKET, -# socket.SO_REUSEADDR, 1) -# SecureXMLRPCServer.SecureXMLRPCServer.server_bind(self) -# -# self.server = SecureThreadedXMLRPCServer((interface, port), -# SecureXMLRPCRequestHandler,0) - raise + server_class = SecureThreadedXMLRPCServer + if socket.has_ipv6: + try: + socket.getaddrinfo(interface or None, port, socket.AF_INET6) + server_class = SecureThreadedXMLRPCServer6 + if not interface: + interface = '::' + except: + pass + if not interface: + interface = '0.0.0.0' + self.server = server_class((interface, port), + SecureXMLRPCRequestHandler, 0) else: server_class = SimpleThreadedXMLRPCServer if socket.has_ipv6: @@ -342,11 +361,12 @@ class HttpDaemon(threading.Thread): class TinySocketClientThread(threading.Thread): - def __init__(self, sock, threads): + def __init__(self, sock, threads, secure): threading.Thread.__init__(self) self.sock = sock self.threads = threads self.running = False + self.secure = secure def run(self): self.running = True @@ -384,8 +404,7 @@ class TinySocketClientThread(threading.Thread): tback = sys.exc_info()[2] pdb.post_mortem(tback) pysocket.send(str(exp), exception=True, traceback=tb_s) - self.sock.shutdown(socket.SHUT_RDWR) - self.sock.close() + pysocket.disconnect() self.threads.remove(self) return True @@ -409,6 +428,12 @@ class TinySocketServerThread(threading.Thread): interface = '0.0.0.0' self.socket = socket.socket(familly, socket.SOCK_STREAM) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + if secure: + from OpenSSL import SSL + ctx = SSL.Context(SSL.SSLv23_METHOD) + ctx.use_privatekey_file(CONFIG['privatekey']) + ctx.use_certificate_file(CONFIG['certificate']) + self.socket = SSL.Connection(ctx, self.socket) self.socket.bind((interface, port)) self.socket.listen(5) self.threads = [] @@ -420,7 +445,7 @@ class TinySocketServerThread(threading.Thread): self.running = True while self.running: (clientsocket, address) = self.socket.accept() - c_thread = TinySocketClientThread(clientsocket, self.threads) + c_thread = TinySocketClientThread(clientsocket, self.threads, self.secure) self.threads.append(c_thread) c_thread.start() self.socket.close() @@ -433,10 +458,16 @@ class TinySocketServerThread(threading.Thread): for thread in self.threads: thread.stop() try: - if hasattr(socket, 'SHUT_RDWR'): - self.socket.shutdown(socket.SHUT_RDWR) + if self.secure: + if hasattr(socket, 'SHUT_RDWR'): + self.socket.sock_shutdown(socket.SHUT_RDWR) + else: + self.socket.sock_shutdown(2) else: - self.socket.shutdown(2) + if hasattr(socket, 'SHUT_RDWR'): + self.socket.shutdown(socket.SHUT_RDWR) + else: + self.socket.shutdown(2) self.socket.close() except: return False diff --git a/trytond/pysocket.py b/trytond/pysocket.py index 2b00b89a38..9880f8cd84 100644 --- a/trytond/pysocket.py +++ b/trytond/pysocket.py @@ -28,6 +28,8 @@ class PySocket: self.sock = sock self.host = None self.port = None + self.ssl = False + self.ssl_sock = None def connect(self, host, port=False): if not port: @@ -47,15 +49,38 @@ class PySocket: self.sock.settimeout(120) self.sock.connect((host, int(port))) DNS_CACHE[host], port = self.sock.getpeername()[:2] + try: + familly = socket.AF_INET + if socket.has_ipv6: + try: + socket.getaddrinfo(host, int(port), socket.AF_INET6) + familly = socket.AF_INET6 + except: + pass + sock = socket.socket(familly, socket.SOCK_STREAM) + sock.settimeout(120) + sock.connect((host, int(port))) + ssl_sock = socket.ssl(sock) + self.ssl = True + except: + pass + if self.ssl: + self.ssl_sock = socket.ssl(self.sock) self.host = host self.port = port def disconnect(self): try: - self.sock.shutdown(socket.SHUT_RDWR) - except: - pass - try: + if self.secure: + if hasattr(socket, 'SHUT_RDWR'): + self.socket.sock_shutdown(socket.SHUT_RDWR) + else: + self.socket.sock_shutdown(2) + else: + if hasattr(socket, 'SHUT_RDWR'): + self.socket.shutdown(socket.SHUT_RDWR) + else: + self.socket.shutdown(2) self.sock.close() except: pass @@ -75,15 +100,26 @@ class PySocket: familly, socket.SOCK_STREAM) self.sock.settimeout(120) self.sock.connect((self.host, int(self.port))) + if self.ssl: + self.ssl_sock = socket.ssl(self.sock) def send(self, msg, exception=False, traceback=None): msg = cPickle.dumps([msg, traceback]) size = len(msg) - self.sock.send('%8d' % size) - self.sock.send(exception and "1" or "0") + if self.ssl: + self.ssl_sock.write('%8d' % size) + else: + self.sock.send('%8d' % size) + if self.ssl: + self.ssl_sock.write(exception and "1" or "0") + else: + self.sock.send(exception and "1" or "0") totalsent = 0 while totalsent < size: - sent = self.sock.send(msg[totalsent:]) + if self.ssl: + sent = self.ssl_sock.write(msg[totalsent:]) + else: + sent = self.sock.send(msg[totalsent:]) if sent == 0: raise RuntimeError, "socket connection broken" totalsent = totalsent + sent @@ -91,19 +127,28 @@ class PySocket: def receive(self): buf = '' while len(buf) < 8: - chunk = self.sock.recv(8 - len(buf)) + if self.ssl: + chunk = self.ssl_sock.read(8 - len(buf)) + else: + chunk = self.sock.recv(8 - len(buf)) if chunk == '': raise RuntimeError, "socket connection broken" buf += chunk size = int(buf) - buf = self.sock.recv(1) + if self.ssl: + buf = self.ssl_sock.read(1) + else: + buf = self.sock.recv(1) if buf != "0": exception = buf else: exception = False msg = '' while len(msg) < size: - chunk = self.sock.recv(size-len(msg)) + if self.ssl: + chunk = self.ssl_sock.read(size-len(msg)) + else: + chunk = self.sock.recv(size-len(msg)) if chunk == '': raise RuntimeError, "socket connection broken" msg = msg + chunk diff --git a/trytond/trytond.py b/trytond/trytond.py index f67dccaa26..2ad3bb1b5e 100644 --- a/trytond/trytond.py +++ b/trytond/trytond.py @@ -157,7 +157,7 @@ class TrytonServer(object): sys.exit(1) tinysocket = netsvc.TinySocketServerThread(interface, port, - False) + secure) self.logger.notify_channel("web-services", netsvc.LOG_INFO, "starting netrpc service, port " + str(port))