From patchwork Wed Oct 5 12:20:24 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Numan Siddique X-Patchwork-Id: 678417 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from archives.nicira.com (archives.nicira.com [96.126.127.54]) by ozlabs.org (Postfix) with ESMTP id 3spvxr35jVz9ryv for ; Wed, 5 Oct 2016 23:20:32 +1100 (AEDT) Received: from archives.nicira.com (localhost [127.0.0.1]) by archives.nicira.com (Postfix) with ESMTP id 731C910650; Wed, 5 Oct 2016 05:20:30 -0700 (PDT) X-Original-To: dev@openvswitch.org Delivered-To: dev@openvswitch.org Received: from mx3v3.cudamail.com (mx3.cudamail.com [64.34.241.5]) by archives.nicira.com (Postfix) with ESMTPS id 6B69C1064C for ; Wed, 5 Oct 2016 05:20:29 -0700 (PDT) Received: from bar6.cudamail.com (localhost [127.0.0.1]) by mx3v3.cudamail.com (Postfix) with ESMTPS id F0470162AE3 for ; Wed, 5 Oct 2016 06:20:28 -0600 (MDT) X-ASG-Debug-ID: 1475670027-0b32375315e0130001-byXFYA Received: from mx3-pf1.cudamail.com ([192.168.14.2]) by bar6.cudamail.com with ESMTP id rG82r6k26zOFicuC (version=TLSv1 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO) for ; Wed, 05 Oct 2016 06:20:27 -0600 (MDT) X-Barracuda-Envelope-From: nusiddiq@redhat.com X-Barracuda-RBL-Trusted-Forwarder: 192.168.14.2 Received: from unknown (HELO mx1.redhat.com) (209.132.183.28) by mx3-pf1.cudamail.com with ESMTPS (DHE-RSA-AES256-SHA encrypted); 5 Oct 2016 12:20:27 -0000 Received-SPF: pass (mx3-pf1.cudamail.com: SPF record at _spf1.redhat.com designates 209.132.183.28 as permitted sender) X-Barracuda-Apparent-Source-IP: 209.132.183.28 X-Barracuda-RBL-IP: 209.132.183.28 Received: from int-mx13.intmail.prod.int.phx2.redhat.com (int-mx13.intmail.prod.int.phx2.redhat.com [10.5.11.26]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id A0657C05A296 for ; Wed, 5 Oct 2016 12:20:26 +0000 (UTC) Received: from nusiddiq.blr.redhat.com (dhcp-0-75.blr.redhat.com [10.70.1.75]) by int-mx13.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id u95CKPgk028240 for ; Wed, 5 Oct 2016 08:20:25 -0400 X-CudaMail-Envelope-Sender: nusiddiq@redhat.com From: Numan Siddique X-CudaMail-Whitelist-To: dev@openvswitch.org X-CudaMail-MID: CM-V1-1004007177 X-CudaMail-DTE: 100516 X-CudaMail-Originating-IP: 209.132.183.28 To: ovs dev X-ASG-Orig-Subj: [##CM-V1-1004007177##][PATCH v2] python: Add SSL support to the python ovs client library Organization: Red Hat Message-ID: Date: Wed, 5 Oct 2016 17:50:24 +0530 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.2.0 MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.68 on 10.5.11.26 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.32]); Wed, 05 Oct 2016 12:20:26 +0000 (UTC) X-Barracuda-Connect: UNKNOWN[192.168.14.2] X-Barracuda-Start-Time: 1475670027 X-Barracuda-Encrypted: DHE-RSA-AES256-SHA X-Barracuda-URL: https://web.cudamail.com:443/cgi-mod/mark.cgi X-ASG-Whitelist: Header =?UTF-8?B?eFwtY3VkYW1haWxcLXdoaXRlbGlzdFwtdG8=?= X-Virus-Scanned: by bsmtpd at cudamail.com X-Barracuda-BRTS-Status: 1 Subject: [ovs-dev] [PATCH v2] python: Add SSL support to the python ovs client library X-BeenThere: dev@openvswitch.org X-Mailman-Version: 2.1.16 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@openvswitch.org Sender: "dev" SSL support is added to the ovs/stream.py. pyOpenSSL library is used to support SSL. If this library is not present, then the SSL stream is not registered with the Stream class. Signed-off-by: Numan Siddique --- python/ovs/poller.py | 8 +++++ python/ovs/stream.py | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++-- tests/ovsdb-idl.at | 30 +++++++++++++++-- tests/test-ovsdb.py | 7 ++++ 4 files changed, 131 insertions(+), 5 deletions(-) v1 -> v2 -------- * Fixed the TypeError exception seen with pyopenssl version 0.14 diff --git a/python/ovs/poller.py b/python/ovs/poller.py index de6bf22..d7cb7d3 100644 --- a/python/ovs/poller.py +++ b/python/ovs/poller.py @@ -20,6 +20,11 @@ import socket import os try: + from OpenSSL import SSL +except ImportError: + SSL = None + +try: import eventlet.patcher def _using_eventlet_green_select(): @@ -54,6 +59,9 @@ class _SelectSelect(object): def register(self, fd, events): if isinstance(fd, socket.socket): fd = fd.fileno() + if SSL and isinstance(fd, SSL.Connection): + fd = fd.fileno() + assert isinstance(fd, int) if events & POLLIN: self.rlist.append(fd) diff --git a/python/ovs/stream.py b/python/ovs/stream.py index 97b22ac..59578b1 100644 --- a/python/ovs/stream.py +++ b/python/ovs/stream.py @@ -22,6 +22,11 @@ import ovs.poller import ovs.socket_util import ovs.vlog +try: + from OpenSSL import SSL +except ImportError: + SSL = None + vlog = ovs.vlog.Vlog("stream") @@ -39,7 +44,7 @@ def stream_or_pstream_needs_probes(name): class Stream(object): - """Bidirectional byte stream. Currently only Unix domain sockets + """Bidirectional byte stream. Unix domain sockets, tcp and ssl are implemented.""" # States. @@ -54,6 +59,10 @@ class Stream(object): _SOCKET_METHODS = {} + _SSL_private_key_file = None + _SSL_certificate_file = None + _SSL_ca_cert_file = None + @staticmethod def register_method(method, cls): Stream._SOCKET_METHODS[method + ":"] = cls @@ -68,7 +77,7 @@ class Stream(object): @staticmethod def is_valid_name(name): """Returns True if 'name' is a stream name in the form "TYPE:ARGS" and - TYPE is a supported stream type (currently only "unix:" and "tcp:"), + TYPE is a supported stream type ("unix:", "tcp:" and "ssl:"), otherwise False.""" return bool(Stream._find_method(name)) @@ -116,7 +125,7 @@ class Stream(object): return error, None else: status = ovs.socket_util.check_connection_completion(sock) - return 0, Stream(sock, name, status) + return 0, cls(sock, name, status) @staticmethod def _open(suffix, dscp): @@ -264,6 +273,18 @@ class Stream(object): # Don't delete the file: we might have forked. self.socket.close() + @staticmethod + def ssl_set_private_key_file(file_name): + Stream._SSL_private_key_file = file_name + + @staticmethod + def ssl_set_certificate_file(file_name): + Stream._SSL_certificate_file = file_name + + @staticmethod + def ssl_set_ca_cert_file(file_name): + Stream._SSL_ca_cert_file = file_name + class PassiveStream(object): @staticmethod @@ -362,6 +383,7 @@ def usage(name): Active %s connection methods: unix:FILE Unix domain socket named FILE tcp:IP:PORT TCP socket to IP with port no of PORT + ssl:IP:PORT SSL socket to IP with port no of PORT Passive %s connection methods: punix:FILE Listen on Unix domain socket FILE""" % (name, name) @@ -385,3 +407,66 @@ class TCPStream(Stream): sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) return error, sock Stream.register_method("tcp", TCPStream) + + +class SSLStream(Stream): + + @staticmethod + def verify_cb(conn, cert, errnum, depth, ok): + return ok + + @staticmethod + def _open(suffix, dscp): + error, sock = TCPStream._open(suffix, dscp) + if error: + return error, None + + # Create an SSL context + ctx = SSL.Context(SSL.SSLv23_METHOD) + ctx.set_verify(SSL.VERIFY_PEER, SSLStream.verify_cb) + ctx.set_options(SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3) + ctx.set_session_cache_mode(SSL.SESS_CACHE_OFF) + # If the client has not set the SSL configuration files + # exception would be raised. + ctx.use_privatekey_file(Stream._SSL_private_key_file) + ctx.use_certificate_file(Stream._SSL_certificate_file) + ctx.load_verify_locations(Stream._SSL_ca_cert_file) + + ssl_sock = SSL.Connection(ctx, sock) + ssl_sock.set_connect_state() + return error, ssl_sock + + def connect(self): + retval = super(SSLStream, self).connect() + + if retval: + return retval + + # TCP Connection is successful. Now do the SSL handshake + try: + self.socket.do_handshake() + except SSL.WantReadError: + return errno.EAGAIN + + return 0 + + def recv(self, n): + try: + return super(SSLStream, self).recv(n) + except SSL.WantReadError: + return (errno.EAGAIN, "") + + def send(self, buf): + try: + if isinstance(buf, six.text_type): + # Convert to byte stream if the buffer is string type/unicode. + # pyopenssl version 0.14 expects the buffer to be byte string. + buf = buf.encode('utf-8') + return super(SSLStream, self).send(buf) + except SSL.WantWriteError: + return errno.EAGAIN + + +if SSL: + # Register SSL only if the OpenSSL module is available + Stream.register_method("ssl", SSLStream) diff --git a/tests/ovsdb-idl.at b/tests/ovsdb-idl.at index d633dbb..e57a3a4 100644 --- a/tests/ovsdb-idl.at +++ b/tests/ovsdb-idl.at @@ -1198,10 +1198,36 @@ m4_define([OVSDB_CHECK_IDL_NOTIFY_PY], OVSDB_SERVER_SHUTDOWN AT_CLEANUP]) +# This test uses the Python IDL implementation with ssl +m4_define([OVSDB_CHECK_IDL_NOTIFY_SSL_PY], + [AT_SETUP([$1 - SSL]) + AT_SKIP_IF([test $HAVE_PYTHON = no]) + $PYTHON -m OpenSSL.SSL + SSL_PRESENT=$? + AT_SKIP_IF([test $SSL_PRESENT != 0]) + AT_KEYWORDS([ovsdb server idl Python notify - ssl socket]) + AT_CHECK([ovsdb-tool create db $abs_srcdir/idltest.ovsschema], + [0], [stdout], [ignore]) + PKIDIR=$abs_top_builddir/tests + AT_CHECK([ovsdb-server --log-file '-vPATTERN:console:ovsdb-server|%c|%m' \ + --detach --no-chdir --pidfile="`pwd`"/pid \ + --private-key=$PKIDIR/testpki-privkey2.pem \ + --certificate=$PKIDIR/testpki-cert2.pem \ + --ca-cert=$PKIDIR/testpki-cacert.pem \ + --remote=pssl:0:127.0.0.1 --unixctl="`pwd`"/unixctl db], [0], [ignore], [ignore]) + PARSE_LISTENING_PORT([ovsdb-server.log], [TCP_PORT]) + AT_CHECK([$PYTHON $srcdir/test-ovsdb.py -t10 idl $srcdir/idltest.ovsschema \ + ssl:127.0.0.1:$TCP_PORT $PKIDIR/testpki-privkey.pem \ + $PKIDIR/testpki-cert.pem $PKIDIR/testpki-cacert.pem $2], + [0], [stdout], [ignore], [kill `cat pid`]) + AT_CHECK([sort stdout | ${PERL} $srcdir/uuidfilt.pl]m4_if([$5],,, [[| $5]]), + [0], [$3], [], [kill `cat pid`]) + OVSDB_SERVER_SHUTDOWN + AT_CLEANUP]) m4_define([OVSDB_CHECK_IDL_NOTIFY], - [OVSDB_CHECK_IDL_NOTIFY_PY($@)]) - + [OVSDB_CHECK_IDL_NOTIFY_PY($@) + OVSDB_CHECK_IDL_NOTIFY_SSL_PY($@)]) OVSDB_CHECK_IDL_NOTIFY([simple idl verify notify], [['track-notify' \ diff --git a/tests/test-ovsdb.py b/tests/test-ovsdb.py index e1cfdad..b27ad28 100644 --- a/tests/test-ovsdb.py +++ b/tests/test-ovsdb.py @@ -27,6 +27,7 @@ from ovs.db import data import ovs.db.types import ovs.ovsuuid import ovs.poller +import ovs.stream import ovs.util from ovs.fatal_signal import signal_alarm import six @@ -519,6 +520,12 @@ def do_idl(schema_file, remote, *commands): schema_helper = ovs.db.idl.SchemaHelper(schema_file) track_notify = False + if remote.startswith("ssl:"): + ovs.stream.Stream.ssl_set_private_key_file(commands[0]) + ovs.stream.Stream.ssl_set_certificate_file(commands[1]) + ovs.stream.Stream.ssl_set_ca_cert_file(commands[2]) + commands = commands[3:] + if commands and commands[0] == "track-notify": commands = commands[1:] track_notify = True