Merge "Replace eventlet with native socket/ssl in nsd4 backend"
This commit is contained in:
@@ -19,9 +19,9 @@
|
||||
# under the License.
|
||||
|
||||
import random
|
||||
import socket
|
||||
import ssl
|
||||
|
||||
import eventlet
|
||||
from oslo_log import log as logging
|
||||
|
||||
from designate.backend import base
|
||||
@@ -49,18 +49,42 @@ class NSD4Backend(base.Backend):
|
||||
'/etc/nsd/nsd_control.key')
|
||||
self.pattern = self.options.get('pattern', 'slave')
|
||||
|
||||
def _create_ssl_context(self):
|
||||
"""Create and configure SSL context with backend settings."""
|
||||
context = ssl.create_default_context()
|
||||
context.load_cert_chain(certfile=self.certfile, keyfile=self.keyfile)
|
||||
|
||||
verify_ssl = (
|
||||
self.options.get('verify_ssl', 'true').lower() == 'true'
|
||||
)
|
||||
check_hostname = (
|
||||
self.options.get('check_hostname', 'true').lower() == 'true'
|
||||
)
|
||||
ca_certs = self.options.get('ca_certs', None)
|
||||
|
||||
context.check_hostname = check_hostname
|
||||
if verify_ssl:
|
||||
context.verify_mode = ssl.CERT_REQUIRED
|
||||
if ca_certs:
|
||||
context.load_verify_locations(cafile=ca_certs)
|
||||
else:
|
||||
context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
return context
|
||||
|
||||
def _command(self, command):
|
||||
sock = eventlet.wrap_ssl(
|
||||
eventlet.connect((self.host, self.port)),
|
||||
keyfile=self.keyfile,
|
||||
certfile=self.certfile)
|
||||
stream = sock.makefile()
|
||||
stream.write(f'{self.NSDCT_VERSION} {command}\n')
|
||||
stream.flush()
|
||||
result = stream.read()
|
||||
stream.close()
|
||||
sock.close()
|
||||
return result
|
||||
"""Execute a command on the NSD4 control channel."""
|
||||
context = self._create_ssl_context()
|
||||
sock_addr = (self.host, self.port)
|
||||
|
||||
with (
|
||||
socket.create_connection(sock_addr) as raw_sock,
|
||||
context.wrap_socket(raw_sock, server_hostname=self.host) as sock,
|
||||
sock.makefile(mode='rw') as stream
|
||||
):
|
||||
stream.write(f'{self.NSDCT_VERSION} {command}\n')
|
||||
stream.flush()
|
||||
return stream.read()
|
||||
|
||||
def _execute_nsd4(self, command):
|
||||
try:
|
||||
|
||||
@@ -18,7 +18,6 @@ import socket
|
||||
import ssl
|
||||
from unittest import mock
|
||||
|
||||
import eventlet
|
||||
import oslotest.base
|
||||
|
||||
from designate.backend import impl_nsd4
|
||||
@@ -65,13 +64,26 @@ class NSD4BackendTestCase(oslotest.base.BaseTestCase):
|
||||
objects.PoolTarget.from_dict(self.target)
|
||||
)
|
||||
|
||||
@mock.patch.object(eventlet, 'connect')
|
||||
@mock.patch.object(eventlet, 'wrap_ssl')
|
||||
def _test_command(self, mock_ssl, mock_connect, command_context):
|
||||
@mock.patch('socket.create_connection')
|
||||
@mock.patch('ssl.create_default_context')
|
||||
def _test_command(self, mock_ssl_context, mock_connect, command_context):
|
||||
raw_sock = mock.MagicMock()
|
||||
raw_sock.__enter__ = mock.MagicMock(return_value=raw_sock)
|
||||
raw_sock.__exit__ = mock.MagicMock(return_value=False)
|
||||
|
||||
sock = mock.MagicMock()
|
||||
sock.__enter__ = mock.MagicMock(return_value=sock)
|
||||
sock.__exit__ = mock.MagicMock(return_value=False)
|
||||
|
||||
stream = mock.MagicMock()
|
||||
mock_connect.return_value = mock.sentinel.client
|
||||
mock_ssl.return_value = sock
|
||||
stream.__enter__ = mock.MagicMock(return_value=stream)
|
||||
stream.__exit__ = mock.MagicMock(return_value=False)
|
||||
|
||||
context = mock.MagicMock()
|
||||
|
||||
mock_connect.return_value = raw_sock
|
||||
mock_ssl_context.return_value = context
|
||||
context.wrap_socket.return_value = sock
|
||||
sock.makefile.return_value = stream
|
||||
if command_context == 'create_fail':
|
||||
stream.read.return_value = 'goat'
|
||||
@@ -91,13 +103,8 @@ class NSD4BackendTestCase(oslotest.base.BaseTestCase):
|
||||
command = 'NSDCT1 addzone %s test-pattern\n' % self.zone.name
|
||||
|
||||
stream.write.assert_called_once_with(command)
|
||||
mock_ssl.assert_called_once_with(mock.sentinel.client,
|
||||
certfile=mock.sentinel.cert.name,
|
||||
keyfile=mock.sentinel.key.name)
|
||||
mock_connect.assert_called_once_with(('127.0.0.1', self.port))
|
||||
sock.makefile.assert_called_once_with()
|
||||
sock.close.assert_called_once_with()
|
||||
stream.close.assert_called_once_with()
|
||||
sock.makefile.assert_called_once_with(mode='rw')
|
||||
stream.flush.assert_called_once_with()
|
||||
stream.read.assert_called_once_with()
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
The NSD4 backend has been migrated from eventlet to native Python
|
||||
socket/ssl libraries. SSL certificate verification is now configurable
|
||||
and enabled by default.
|
||||
|
||||
New configuration options:
|
||||
|
||||
- ``verify_ssl``: Enable/disable SSL certificate verification (default: ``true``)
|
||||
- ``check_hostname``: Enable/disable hostname checking (default: ``true``)
|
||||
- ``ca_certs``: Path to CA certificate bundle (default: system CA bundle)
|
||||
|
||||
**Breaking Change:** Deployments using self-signed certificates must
|
||||
explicitly disable verification or add certificates to the system trust
|
||||
store.
|
||||
|
||||
Configure these options in your pools.yaml file under the NSD4 target's
|
||||
``options`` section, then run ``designate-manage pool update``.
|
||||
To maintain previous insecure behavior:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
targets:
|
||||
- type: nsd4
|
||||
options:
|
||||
host: 192.0.2.2
|
||||
port: 8952
|
||||
verify_ssl: false
|
||||
check_hostname: false
|
||||
|
||||
Deployments with properly signed certificates require no configuration
|
||||
changes.
|
||||
security:
|
||||
- |
|
||||
The NSD4 backend now defaults to secure SSL certificate verification.
|
||||
Previously, certificate verification and hostname checking were
|
||||
unconditionally disabled.
|
||||
Reference in New Issue
Block a user