Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions .zuul.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -869,13 +869,6 @@
irrelevant-files: *nova-base-irrelevant-files
- openstacksdk-functional-devstack:
irrelevant-files: *nova-base-irrelevant-files
periodic-weekly:
jobs:
# Runs emulation feature functionality test less frequently due
# to being the initial release and experimental in nature.
- nova-emulation
- ironic-tempest-ipa-wholedisk-direct-tinyipa-multinode-shard
- tempest-centos9-stream-fips
experimental:
jobs:
- ironic-tempest-bfv:
Expand Down
4 changes: 4 additions & 0 deletions nova/compute/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2204,6 +2204,10 @@ def create(
msg = _('The requested availability zone is not available')
raise exception.InvalidRequest(msg)

if scheduler_hints:
scheduler_hints = {k: v for k, v in scheduler_hints.items()
if not k.startswith('_nova')}

filter_properties = scheduler_utils.build_filter_properties(
scheduler_hints, forced_host, forced_node, flavor)

Expand Down
2 changes: 1 addition & 1 deletion nova/console/websocketproxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def new_websocket_client(self):
expected_origin_hostname = e.split(']')[0][1:]
else:
expected_origin_hostname = e.split(':')[0]
expected_origin_hostnames = CONF.console.allowed_origins
expected_origin_hostnames = list(CONF.console.allowed_origins)
expected_origin_hostnames.append(expected_origin_hostname)
origin_url = self.headers.get('Origin')
# missing origin header indicates non-browser client which is OK
Expand Down
18 changes: 18 additions & 0 deletions nova/tests/unit/compute/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,24 @@ def _obj_to_list_obj(self, list_obj, obj):
list_obj.obj_reset_changes()
return list_obj

@mock.patch('nova.scheduler.utils.build_filter_properties')
def test_create_strips_internal_scheduler_hints(self,
mock_build_filter):
mock_build_filter.side_effect = (
test.TestingException('stop early'))
flavor = self._create_flavor()
self.assertRaises(
test.TestingException,
self.compute_api.create,
self.context, flavor, 'image_id',
scheduler_hints={
'_nova_check_type': 'rebuild',
'_nova_future': 'something',
'group': 'valid-group-uuid',
})
actual_hints = mock_build_filter.call_args[0][0]
self.assertEqual({'group': 'valid-group-uuid'}, actual_hints)

@mock.patch(
'nova.network.neutron.API.is_remote_managed_port',
new=mock.Mock(return_value=False),
Expand Down
75 changes: 75 additions & 0 deletions nova/tests/unit/console/test_websocketproxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,81 @@ def test_reject_open_redirect(self, url='//example.com/%2F..'):
def test_reject_open_redirect_3_slashes(self):
self.test_reject_open_redirect(url='///example.com/%2F..')

@mock.patch('nova.console.websocketproxy.NovaProxyRequestHandler.'
'_check_console_port')
@mock.patch('nova.objects.ConsoleAuthToken.validate')
def test_host_header_does_not_poison_allowed_origins(
self, validate, check_port):
"""Verify that the Host header from one request does not persist in
CONF.console.allowed_origins and affect subsequent origin checks.

Regression test for bug 2158919.
"""
params = {
'id': 1,
'token': '123-456-789',
'instance_uuid': uuids.instance,
'host': 'node1',
'port': '10000',
'console_type': 'novnc',
'access_url_base': 'https://example.net:6080'
}
validate.return_value = objects.ConsoleAuthToken(**params)

self.wh.socket.return_value = '<socket>'
self.wh.path = "http://127.0.0.1/?token=123-456-789"
self.wh.headers = self.fake_header

original_conf_origins = list(CONF.console.allowed_origins)

self.wh.new_websocket_client()

self.assertEqual(original_conf_origins,
CONF.console.allowed_origins)

@mock.patch('nova.console.websocketproxy.NovaProxyRequestHandler.'
'_check_console_port')
@mock.patch('nova.objects.ConsoleAuthToken.validate')
def test_previous_host_does_not_bypass_origin_check(
self, validate, check_port):
"""Verify that a Host header from a prior request cannot be used to
bypass the origin check on a subsequent request.

Regression test for bug 2158919.
"""
params = {
'id': 1,
'token': '123-456-789',
'instance_uuid': uuids.instance,
'host': 'node1',
'port': '10000',
'console_type': 'novnc',
'access_url_base': 'https://example.net:6080'
}
validate.return_value = objects.ConsoleAuthToken(**params)

self.wh.socket.return_value = '<socket>'
self.wh.path = "http://127.0.0.1/?token=123-456-789"

# First request: Host header introduces evil.com
self.wh.headers = {
'cookie': 'token="123-456-789"',
'Origin': 'https://evil.com:6080',
'Host': 'evil.com:6080',
}
self.wh.new_websocket_client()

# Second request: Origin is evil.com but Host is legitimate.
# This must be rejected — evil.com should not have been persisted
# into the allow-list by the first request.
self.wh.headers = {
'cookie': 'token="123-456-789"',
'Origin': 'https://evil.com:6080',
'Host': 'example.net:6080',
}
self.assertRaises(exception.ValidationError,
self.wh.new_websocket_client)

@mock.patch('nova.objects.ConsoleAuthToken.validate')
def test_no_compute_rpcapi_with_invalid_token(self, mock_validate):
"""Tests that we don't create a ComputeAPI object until we actually
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
security:
- |
Bug #2158919 is fixed, which involved an authenticated user able to poison
the server-side allowed origins list (and potentially exhaust memory by
extending it until failure).