From 8806477abfdac68c409056e22f5e0c50791b8a27 Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Tue, 23 Jan 2018 16:47:23 -0500 Subject: [PATCH] Fixes using SSL OVSDB connection When creating SSL OVSDB connection it is required to set the private key, certificate, and the CA certificate in order to communicate with OVSDB. This patch configures these when an SSL connection URI is used. The settings must be provided as part of neutron.conf under [ovs] section. Closes-Bug: 1745038 Change-Id: I19fd9dd0c72260835eb91e557a6029ec9d652179 Signed-off-by: Tim Rozet --- neutron/agent/ovsdb/native/connection.py | 26 ++++++++ neutron/agent/ovsdb/native/exceptions.py | 28 +++++++++ neutron/conf/agent/ovsdb_api.py | 17 +++++- .../agent/ovsdb/native/test_connection.py | 61 +++++++++++++++++++ ...ovsdb-ssl-connection-4058caf4fdcb33ab.yaml | 8 +++ 5 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 neutron/agent/ovsdb/native/exceptions.py create mode 100644 releasenotes/notes/fix-ovsdb-ssl-connection-4058caf4fdcb33ab.yaml diff --git a/neutron/agent/ovsdb/native/connection.py b/neutron/agent/ovsdb/native/connection.py index ca2ab4ede32..7865f031611 100644 --- a/neutron/agent/ovsdb/native/connection.py +++ b/neutron/agent/ovsdb/native/connection.py @@ -12,13 +12,17 @@ # License for the specific language governing permissions and limitations # under the License. +import os + from debtcollector import moves from oslo_config import cfg from ovs.db import idl +from ovs.stream import Stream from ovsdbapp.backend.ovs_idl import connection as _connection from ovsdbapp.backend.ovs_idl import idlutils import tenacity +from neutron.agent.ovsdb.native import exceptions as ovsdb_exc from neutron.agent.ovsdb.native import helpers TransactionQueue = moves.moved_class(_connection.TransactionQueue, @@ -26,9 +30,31 @@ TransactionQueue = moves.moved_class(_connection.TransactionQueue, Connection = moves.moved_class(_connection.Connection, 'Connection', __name__) +def configure_ssl_conn(): + """ + Configures required settings for an SSL based OVSDB client connection + :return: None + """ + + req_ssl_opts = {'ssl_key_file': cfg.CONF.OVS.ssl_key_file, + 'ssl_cert_file': cfg.CONF.OVS.ssl_cert_file, + 'ssl_ca_cert_file': cfg.CONF.OVS.ssl_ca_cert_file} + for ssl_opt, ssl_file in req_ssl_opts.items(): + if not ssl_file: + raise ovsdb_exc.OvsdbSslRequiredOptError(ssl_opt=ssl_opt) + elif not os.path.exists(ssl_file): + raise ovsdb_exc.OvsdbSslConfigNotFound(ssl_file=ssl_file) + # TODO(ihrachys): move to ovsdbapp + Stream.ssl_set_private_key_file(req_ssl_opts['ssl_key_file']) + Stream.ssl_set_certificate_file(req_ssl_opts['ssl_cert_file']) + Stream.ssl_set_ca_cert_file(req_ssl_opts['ssl_ca_cert_file']) + + def idl_factory(): conn = cfg.CONF.OVS.ovsdb_connection schema_name = 'Open_vSwitch' + if conn.startswith('ssl:'): + configure_ssl_conn() try: helper = idlutils.get_schema_helper(conn, schema_name) except Exception: diff --git a/neutron/agent/ovsdb/native/exceptions.py b/neutron/agent/ovsdb/native/exceptions.py new file mode 100644 index 00000000000..46db629304d --- /dev/null +++ b/neutron/agent/ovsdb/native/exceptions.py @@ -0,0 +1,28 @@ +# Copyright 2018 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron_lib import exceptions as e + +from neutron._i18n import _ + + +class OvsdbSslConfigNotFound(e.NeutronException): + message = _("Specified SSL file %(ssl_file)s could not be found") + + +class OvsdbSslRequiredOptError(e.NeutronException): + message = _("Required 'ovs' group option %(ssl_opt)s not set. SSL " + "configuration options are required when using SSL " + "ovsdb_connection URI") diff --git a/neutron/conf/agent/ovsdb_api.py b/neutron/conf/agent/ovsdb_api.py index 1f84000a8ab..7c13a85465f 100644 --- a/neutron/conf/agent/ovsdb_api.py +++ b/neutron/conf/agent/ovsdb_api.py @@ -35,7 +35,22 @@ API_OPTS = [ 'Will be used by ovsdb-client when monitoring and ' 'used for the all ovsdb commands when native ' 'ovsdb_interface is enabled' - )) + )), + cfg.StrOpt('ssl_key_file', + help=_('The SSL private key file to use when interacting with ' + 'OVSDB. Required when using an "ssl:" prefixed ' + 'ovsdb_connection' + )), + cfg.StrOpt('ssl_cert_file', + help=_('The SSL certificate file to use when interacting ' + 'with OVSDB. Required when using an "ssl:" prefixed ' + 'ovsdb_connection' + )), + cfg.StrOpt('ssl_ca_cert_file', + help=_('The Certificate Authority (CA) certificate to use ' + 'when interacting with OVSDB. Required when using an ' + '"ssl:" prefixed ovsdb_connection' + )), ] diff --git a/neutron/tests/unit/agent/ovsdb/native/test_connection.py b/neutron/tests/unit/agent/ovsdb/native/test_connection.py index 090911c3dc2..3b016067f52 100644 --- a/neutron/tests/unit/agent/ovsdb/native/test_connection.py +++ b/neutron/tests/unit/agent/ovsdb/native/test_connection.py @@ -18,9 +18,14 @@ from ovsdbapp.backend.ovs_idl import connection from ovsdbapp.backend.ovs_idl import idlutils from neutron.agent.ovsdb.native import connection as native_conn +from neutron.agent.ovsdb.native import exceptions as ovsdb_exc from neutron.agent.ovsdb.native import helpers from neutron.tests import base +SSL_KEY_FILE = '/tmp/dummy.pem' +SSL_CERT_FILE = '/tmp/dummy.crt' +SSL_CA_FILE = '/tmp/ca.crt' + class TestOVSNativeConnection(base.BaseTestCase): @mock.patch.object(connection, 'threading') @@ -46,3 +51,59 @@ class TestOVSNativeConnection(base.BaseTestCase): conn.start() self.assertEqual(3, len(mock_get_schema_helper.mock_calls)) mock_helper.register_all.assert_called_once_with() + + @mock.patch.object(native_conn, 'Stream') + @mock.patch.object(connection, 'threading') + @mock.patch.object(native_conn, 'idl') + @mock.patch.object(idlutils, 'get_schema_helper') + @mock.patch.object(native_conn, 'os') + @mock.patch.object(native_conn, 'cfg') + def test_ssl_connection(self, mock_cfg, mock_os, mock_get_schema_helper, + mock_idl, mock_threading, mock_stream): + mock_os.path.isfile.return_value = True + mock_cfg.CONF.OVS.ovsdb_connection = 'ssl:127.0.0.1:6640' + mock_cfg.CONF.OVS.ssl_key_file = SSL_KEY_FILE + mock_cfg.CONF.OVS.ssl_cert_file = SSL_CERT_FILE + mock_cfg.CONF.OVS.ssl_ca_cert_file = SSL_CA_FILE + + conn = connection.Connection(idl=native_conn.idl_factory(), + timeout=1) + conn.start() + mock_stream.ssl_set_private_key_file.assert_called_once_with( + SSL_KEY_FILE + ) + mock_stream.ssl_set_certificate_file.assert_called_once_with( + SSL_CERT_FILE + ) + mock_stream.ssl_set_ca_cert_file.assert_called_once_with( + SSL_CA_FILE + ) + + @mock.patch.object(native_conn, 'Stream') + @mock.patch.object(connection, 'threading') + @mock.patch.object(native_conn, 'idl') + @mock.patch.object(idlutils, 'get_schema_helper') + @mock.patch.object(native_conn, 'cfg') + def test_ssl_conn_file_missing(self, mock_cfg, mock_get_schema_helper, + mock_idl, mock_threading, mock_stream): + mock_cfg.CONF.OVS.ovsdb_connection = 'ssl:127.0.0.1:6640' + mock_cfg.CONF.OVS.ssl_key_file = SSL_KEY_FILE + mock_cfg.CONF.OVS.ssl_cert_file = SSL_CERT_FILE + mock_cfg.CONF.OVS.ssl_ca_cert_file = SSL_CA_FILE + + self.assertRaises(ovsdb_exc.OvsdbSslConfigNotFound, + native_conn.idl_factory) + + @mock.patch.object(native_conn, 'Stream') + @mock.patch.object(connection, 'threading') + @mock.patch.object(native_conn, 'idl') + @mock.patch.object(idlutils, 'get_schema_helper') + @mock.patch.object(native_conn, 'cfg') + def test_ssl_conn_cfg_missing(self, mock_cfg, mock_get_schema_helper, + mock_idl, mock_threading, mock_stream): + mock_cfg.CONF.OVS.ovsdb_connection = 'ssl:127.0.0.1:6640' + mock_cfg.CONF.OVS.ssl_key_file = None + mock_cfg.CONF.OVS.ssl_cert_file = None + mock_cfg.CONF.OVS.ssl_ca_cert_file = None + self.assertRaises(ovsdb_exc.OvsdbSslRequiredOptError, + native_conn.idl_factory) diff --git a/releasenotes/notes/fix-ovsdb-ssl-connection-4058caf4fdcb33ab.yaml b/releasenotes/notes/fix-ovsdb-ssl-connection-4058caf4fdcb33ab.yaml new file mode 100644 index 00000000000..6be8e71baed --- /dev/null +++ b/releasenotes/notes/fix-ovsdb-ssl-connection-4058caf4fdcb33ab.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Neutron agents now support SSL connections to OVSDB server. + To enable an SSL based connection, use an ``ssl`` prefixed URI for the + ``ovsdb_connection`` setting. When using SSL it is also required to set + new ``ovs`` group options which include ``ssl_key_file``, ``ssl_cert_file``, and + ``ssl_ca_cert_file``.