@@ -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)
@@ -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)
@@ -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' \
@@ -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
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 <nusiddiq@redhat.com> --- 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