Please note that this is a stop-gap measure. Also add a basic SSL server test.
Co-authored-by: alex <atoptsog...@suse.com> Signed-off-by: IWAMOTO Toshihiro <iwam...@valinux.co.jp> --- ryu/controller/controller.py | 23 +++++++++--- ryu/lib/hub.py | 13 ++++++- ryu/tests/unit/controller/cert.crt | 21 +++++++++++ ryu/tests/unit/controller/cert.key | 28 +++++++++++++++ ryu/tests/unit/controller/test_controller.py | 52 ++++++++++++++++++++++++++++ 5 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 ryu/tests/unit/controller/cert.crt create mode 100644 ryu/tests/unit/controller/cert.key diff --git a/ryu/controller/controller.py b/ryu/controller/controller.py index 62135339..b3d2d35b 100644 --- a/ryu/controller/controller.py +++ b/ryu/controller/controller.py @@ -165,6 +165,23 @@ class OpenFlowController(object): def server_loop(self, ofp_tcp_listen_port, ofp_ssl_listen_port): if CONF.ctl_privkey is not None and CONF.ctl_cert is not None: + if not hasattr(ssl, 'SSLContext'): + # anything less than python 2.7.9 supports only TLSv1 + # or less, thus we choose TLSv1 + ssl_args = {'ssl_version': ssl.PROTOCOL_TLSv1} + else: + # from 2.7.9 and versions 3.4+ ssl context creation is + # supported. Protocol_TLS from 2.7.13 and from 3.5.3 + # replaced SSLv23. Functionality is similar. + if hasattr(ssl, 'PROTOCOL_TLS'): + p = 'PROTOCOL_TLS' + else: + p = 'PROTOCOL_SSLv23' + + ssl_args = {'ssl_ctx': ssl.SSLContext(getattr(ssl, p))} + # Restrict non-safe versions + ssl_args['ssl_ctx'].options |= ssl.OP_NO_SSLv3 | ssl.OP_NO_SSLv2 + if CONF.ca_certs is not None: server = StreamServer((CONF.ofp_listen_host, ofp_ssl_listen_port), @@ -172,15 +189,13 @@ class OpenFlowController(object): keyfile=CONF.ctl_privkey, certfile=CONF.ctl_cert, cert_reqs=ssl.CERT_REQUIRED, - ca_certs=CONF.ca_certs, - ssl_version=ssl.PROTOCOL_TLSv1) + ca_certs=CONF.ca_certs, **ssl_args) else: server = StreamServer((CONF.ofp_listen_host, ofp_ssl_listen_port), datapath_connection_factory, keyfile=CONF.ctl_privkey, - certfile=CONF.ctl_cert, - ssl_version=ssl.PROTOCOL_TLSv1) + certfile=CONF.ctl_cert, **ssl_args) else: server = StreamServer((CONF.ofp_listen_host, ofp_tcp_listen_port), diff --git a/ryu/lib/hub.py b/ryu/lib/hub.py index bd15fc89..e847f656 100644 --- a/ryu/lib/hub.py +++ b/ryu/lib/hub.py @@ -42,6 +42,7 @@ if HUB_TYPE == 'eventlet': import ssl import socket import traceback + import sys getcurrent = eventlet.getcurrent patch = eventlet.monkey_patch @@ -128,7 +129,17 @@ if HUB_TYPE == 'eventlet': if ssl_args: def wrap_and_handle(sock, addr): ssl_args.setdefault('server_side', True) - handle(ssl.wrap_socket(sock, **ssl_args), addr) + if 'ssl_ctx' in ssl_args: + ctx = ssl_args.pop('ssl_ctx') + ctx.load_cert_chain(ssl_args.pop('certfile'), + ssl_args.pop('keyfile')) + if 'cert_reqs' in ssl_args: + ctx.verify_mode = ssl_args.pop('cert_reqs') + if 'ca_certs' in ssl_args: + ctx.load_verify_locations(ssl_args.pop('ca_certs')) + handle(ctx.wrap_socket(sock, **ssl_args), addr) + else: + handle(ssl.wrap_socket(sock, **ssl_args), addr) self.handle = wrap_and_handle else: diff --git a/ryu/tests/unit/controller/cert.crt b/ryu/tests/unit/controller/cert.crt new file mode 100644 index 00000000..e1b2afc4 --- /dev/null +++ b/ryu/tests/unit/controller/cert.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDaDCCAlCgAwIBAgIJAKL09YuU92JPMA0GCSqGSIb3DQEBCwUAMEgxCzAJBgNV +BAYTAkpQMRMwEQYDVQQIDApTb21lLVN0YXRlMSQwIgYDVQQKDBtSeXUgU0ROIEZy +YW1ld29yayBDb21tdW5pdHkwIBcNMTkwMzI1MDE1NzQzWhgPMjI5MzAxMDYwMTU3 +NDNaMEgxCzAJBgNVBAYTAkpQMRMwEQYDVQQIDApTb21lLVN0YXRlMSQwIgYDVQQK +DBtSeXUgU0ROIEZyYW1ld29yayBDb21tdW5pdHkwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQDLT29+6JwD75wH7gPVxU52hrysBfxE6WjyT/nT+aSIQmZu +SU6/5hECOnV4YdyB7rxFu2WO2SD5PgeoHPBpTqtxrdTWoVOWVljnNcqEwSCS7bl9 +nbgX8uxCacg9qbFNJJRBAS0XQ2bSsD0GoOnhj3Olrz1u0wRIUqrR3A5giMbYwQPr +S4cmkxfgp2uV+WCHk40WxZnGgWzIRhO11GK9CAGncncPYhj+23w+GFaHIf00TdV2 +JEvwLFuLf1EaewZ7rz8zf1sLHAxqx20A6VdledEpNAzt1L8goPhk1mHvRgUC7E2v +FnSt1ePCJsVrvccudMdPBXSMfgJC2gmfdQefdSXRAgMBAAGjUzBRMB0GA1UdDgQW +BBRjlXSQ2rVjwOr1io6iJyidmjCNfzAfBgNVHSMEGDAWgBRjlXSQ2rVjwOr1io6i +JyidmjCNfzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCC1Uvo +4PdC5YQSXkAhrxgVhexp87VVkoWwpno75hvfoCjtSkb7+cskvQbPM14zbjIUrsli +qmTkjXyTUe8q5U06OitMAyM6qUvS0nFDi5aPQYV6N0XmJ+rV18prfQORyHvEmEyv +nqHVPoQkmGPpJ8aOVrTlECyxG7wLI2UxBEB3Atk51QHzbGGLKW7g5tHY6J5cMe/9 +ydeClJk2/AXkoqWkbtJrbw46alH97CajuLn/4D9B/Rm+M1Kg48gze5zJ7+WrB0Jl +pAhRqMM3upaOlXdeYDdNDgE0j/ulZGY2UssFIoHylcrb4QKQXjwqRXYhuuucJQJ3 +vsY4y1D/qps9llRL +-----END CERTIFICATE----- diff --git a/ryu/tests/unit/controller/cert.key b/ryu/tests/unit/controller/cert.key new file mode 100644 index 00000000..2b7f5f06 --- /dev/null +++ b/ryu/tests/unit/controller/cert.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQDLT29+6JwD75wH +7gPVxU52hrysBfxE6WjyT/nT+aSIQmZuSU6/5hECOnV4YdyB7rxFu2WO2SD5Pgeo +HPBpTqtxrdTWoVOWVljnNcqEwSCS7bl9nbgX8uxCacg9qbFNJJRBAS0XQ2bSsD0G +oOnhj3Olrz1u0wRIUqrR3A5giMbYwQPrS4cmkxfgp2uV+WCHk40WxZnGgWzIRhO1 +1GK9CAGncncPYhj+23w+GFaHIf00TdV2JEvwLFuLf1EaewZ7rz8zf1sLHAxqx20A +6VdledEpNAzt1L8goPhk1mHvRgUC7E2vFnSt1ePCJsVrvccudMdPBXSMfgJC2gmf +dQefdSXRAgMBAAECggEAcvPsB6Z/qB4Pa9Bg7LqNnaia/uy3cUND6lXb3MW3CK/6 +eHsMgqYTkd3502IJqpGQdCD70CPmZ+Zxr9UE/ZXUjAcMY3p952/U/o3EfwEvaMPu +8B6AG1Jn0Tk8VdkffY2kIYkHtLKQbanmJ1xOQRG6AsEti/7V2gqbuOKiYmSTgbPG +Upw9JNdtR6bZrGrrEXJbPCrSCej47MDyE+nt4zMIsqmY5IlbTMHcTKVDGeKbT9qT +7/Uyg+Tb62eber9iQhE0OteLt2GwrJR5yZ5QKNKM4SPqwYlOvQ9z289eZMVU3uwI +1NI1YRM5EMsdWrzFye7H/T/jsCaWrO0zmI/I4BMfEQKBgQDtUgPyY4PgVXVZ/hha +l5pi66GQ79+6LJP3SHb3I6p0iULq3oV+onG0Ezvx2vc21sbuLEazNlJoXzEzSIVM +/RjNJ9FsD/ENEuJedkspwtZZ4O4ZH6wKyHg/LCUly59ER37Ql/XwIX7adKCn7Z4d +9xN3aQmPtLna/aIZ8HyptRpT9QKBgQDbUB67YXiIFY+k5cwtK0m8T3rY4WNpEwzr +Y/1l+0EvXqCousU9MnLveyY8EcLDh5SnM0CvH4mFS8xL/r/kcUO9cHwuM+KZ77KN +Ukp9CRT9raxDZY/F0FVuET4LrJNnekCMsOnMxO51il/AHcul7886sEirkB1dsXND +nkh9h8g87QJ/cRikyN6j+kS/qCNvd7zH1lx0op2uAQs9eJsQFrbohKDlQwjIlZDU +nvyLlLbFGV1BcD+pcb5xh0vWJppo7EexihNvug/e0FwvhNTa/QvdGvgWf+KYGotu +wqxHB7wCKofn54CDs+xCh9kMtvqGX8FfhYiJBfMan0I//hydTEMCSQKBgEiv6E+g +gYtQ4hf8FczOsRSZnxSstv8HUlvd+wlG2hbyHPtvU5nx04gt38E+/bdCg3FbGlAw +eqrUMXTqjP0Q0SvDUVUa2zq76AjQwmFoli1x10tLKPieEQJ28oJ6Ayzjpus6Y3L7 +vjD02MFa3rkznxJLhPpfvGvmOVaq6km4rBQNAoGBALQGfaRiAtp6lSubi4Etdwtg +Tps2o1SBXfzENpq6s99k+UdCBLh90uzuA897GClsUYeuAYUyxQP3otIZUuSjq/Ht +JHYwT9QxOkSYrNCfQW/nF0CJjZ6TcvcFp8SdyUUbwCR2rkDK7LlMzxkfU3cCrwMP +q51oIVlKjIxg86JJXrRQ +-----END PRIVATE KEY----- diff --git a/ryu/tests/unit/controller/test_controller.py b/ryu/tests/unit/controller/test_controller.py index a94e7b43..45c659c9 100644 --- a/ryu/tests/unit/controller/test_controller.py +++ b/ryu/tests/unit/controller/test_controller.py @@ -22,6 +22,7 @@ except ImportError: import json import os +import ssl import sys import warnings import logging @@ -33,9 +34,11 @@ from nose.tools import eq_, raises from ryu.base import app_manager # To suppress cyclic import from ryu.controller import controller from ryu.controller import handler +from ryu.lib import hub from ryu.ofproto import ofproto_v1_3_parser from ryu.ofproto import ofproto_v1_2_parser from ryu.ofproto import ofproto_v1_0_parser +hub.patch() LOG = logging.getLogger('test_controller') @@ -177,3 +180,52 @@ class Test_Datapath(unittest.TestCase): self.assertEqual(state, handler.MAIN_DISPATCHER) self.assertEqual(kwargs, {}) self.assertEqual(expected_json, output_json) + + +class TestOpenFlowController(unittest.TestCase): + """ + Test cases for OpenFlowController + """ + @mock.patch("ryu.controller.controller.CONF") + def _test_ssl(self, this_dir, port, conf_mock): + conf_mock.ofp_ssl_listen_port = port + conf_mock.ofp_listen_host = "127.0.0.1" + conf_mock.ca_certs = None + conf_mock.ctl_cert = os.path.join(this_dir, 'cert.crt') + conf_mock.ctl_privkey = os.path.join(this_dir, 'cert.key') + c = controller.OpenFlowController() + c() + + def test_ssl(self): + """Tests SSL server functionality.""" + # TODO: TLS version enforcement is necessary to avoid + # vulnerable versions. Currently, this only tests TLS + # connectivity. + this_dir = os.path.dirname(sys.modules[__name__].__file__) + saved_exception = None + try: + ssl_version = ssl.PROTOCOL_TLS + except AttributeError: + # For compatibility with older pythons. + ssl_version = ssl.PROTOCOL_TLSv1 + for i in range(3): + try: + # Try a few times as this can fail with EADDRINUSE + port = random.randint(5000, 10000) + server = hub.spawn(self._test_ssl, this_dir, port) + hub.sleep(1) + client = hub.StreamClient(("127.0.0.1", port), + timeout=5, + ssl_version=ssl_version) + if client.connect() is not None: + break + except Exception as e: + saved_exception = e + continue + finally: + try: + hub.kill(server) + except Exception: + pass + else: + self.fail("Failed to connect: " + str(saved_exception)) -- 2.11.0 _______________________________________________ Ryu-devel mailing list Ryu-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/ryu-devel