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 <trozet@redhat.com>
This commit is contained in:
Tim Rozet 2018-01-23 16:47:23 -05:00 committed by Ihar Hrachyshka
parent c46eb76394
commit 8806477abf
5 changed files with 139 additions and 1 deletions

View File

@ -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:

View File

@ -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")

View File

@ -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'
)),
]

View File

@ -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)

View File

@ -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``.