Prevent localhost from being used as ironic-inspector callback URL
At least bifrost used to do it, so it's better to prevent users from shooting their legs. Change-Id: Idf25d9f434483f023ad7a40b6c242635ab89a804 Story: #1528920
This commit is contained in:
parent
71c03410ae
commit
b6035d6137
@ -15,12 +15,14 @@ Modules required to work with ironic_inspector:
|
|||||||
https://pypi.org/project/ironic-inspector
|
https://pypi.org/project/ironic-inspector
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import ipaddress
|
||||||
import shlex
|
import shlex
|
||||||
|
|
||||||
import eventlet
|
import eventlet
|
||||||
from futurist import periodics
|
from futurist import periodics
|
||||||
import openstack
|
import openstack
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
from six.moves.urllib import parse as urlparse
|
||||||
|
|
||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
from ironic.common.i18n import _
|
from ironic.common.i18n import _
|
||||||
@ -72,12 +74,29 @@ def _get_callback_endpoint(client):
|
|||||||
root = CONF.inspector.callback_endpoint_override or client.get_endpoint()
|
root = CONF.inspector.callback_endpoint_override or client.get_endpoint()
|
||||||
if root == 'mdns':
|
if root == 'mdns':
|
||||||
return root
|
return root
|
||||||
root = root.rstrip('/')
|
|
||||||
|
parts = urlparse.urlsplit(root)
|
||||||
|
is_loopback = False
|
||||||
|
try:
|
||||||
|
# ip_address requires a unicode string on Python 2
|
||||||
|
is_loopback = ipaddress.ip_address(parts.hostname).is_loopback
|
||||||
|
except ValueError: # host name
|
||||||
|
is_loopback = (parts.hostname == 'localhost')
|
||||||
|
|
||||||
|
if is_loopback:
|
||||||
|
raise exception.InvalidParameterValue(
|
||||||
|
_('Loopback address %s cannot be used as an introspection '
|
||||||
|
'callback URL') % parts.hostname)
|
||||||
|
|
||||||
# NOTE(dtantsur): the IPA side is quite picky about the exact format.
|
# NOTE(dtantsur): the IPA side is quite picky about the exact format.
|
||||||
if root.endswith('/v1'):
|
if parts.path.endswith('/v1'):
|
||||||
return '%s/continue' % root
|
add = '/continue'
|
||||||
else:
|
else:
|
||||||
return '%s/v1/continue' % root
|
add = '/v1/continue'
|
||||||
|
|
||||||
|
return urlparse.urlunsplit((parts.scheme, parts.netloc,
|
||||||
|
parts.path.rstrip('/') + add,
|
||||||
|
parts.query, parts.fragment))
|
||||||
|
|
||||||
|
|
||||||
def _tear_down_managed_boot(task):
|
def _tear_down_managed_boot(task):
|
||||||
|
@ -107,6 +107,12 @@ class CommonFunctionsTestCase(BaseTestCase):
|
|||||||
self.assertEqual('mdns', inspector._get_callback_endpoint(client))
|
self.assertEqual('mdns', inspector._get_callback_endpoint(client))
|
||||||
self.assertFalse(client.get_endpoint.called)
|
self.assertFalse(client.get_endpoint.called)
|
||||||
|
|
||||||
|
def test_get_callback_endpoint_no_loopback(self):
|
||||||
|
client = mock.Mock()
|
||||||
|
client.get_endpoint.return_value = 'http://127.0.0.1:5050'
|
||||||
|
self.assertRaisesRegex(exception.InvalidParameterValue, 'Loopback',
|
||||||
|
inspector._get_callback_endpoint, client)
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(eventlet, 'spawn_n', lambda f, *a, **kw: f(*a, **kw))
|
@mock.patch.object(eventlet, 'spawn_n', lambda f, *a, **kw: f(*a, **kw))
|
||||||
@mock.patch('ironic.drivers.modules.inspector._get_client', autospec=True)
|
@mock.patch('ironic.drivers.modules.inspector._get_client', autospec=True)
|
||||||
|
7
releasenotes/notes/no-localhost-596adedfe388dfae.yaml
Normal file
7
releasenotes/notes/no-localhost-596adedfe388dfae.yaml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
upgrade:
|
||||||
|
- |
|
||||||
|
Using localhost as the Bare Metal Introspection endpoint will not work
|
||||||
|
when managed boot is used for in-band inspection. Either update the
|
||||||
|
endpoint to a real IP address or use the new configuration option
|
||||||
|
``[inspector]callback_endpoint_override``.
|
Loading…
x
Reference in New Issue
Block a user