Add console allowed origins setting

Adds a DEFAULT/console_allowed_origins to the console
websocket proxy to allow connections from other Origin hostnames
(other than only the hostname in the Host header.)

Origin header checking was introduced in
https://review.openstack.org/#/c/163033/ as a fix for bug 1409142
and CVE-2015-0259.  However there are valid use cases where you
want to do cross-site web socket connections.

This patch allows for controlled cross-site web socket connections
to the console proxy services.

DocImpact

Change-Id: If7995bb7afc255f5ad834dc8a7044ef38b6cb335
Closes-bug: 1474079
This commit is contained in:
Mike Dorman 2015-07-14 11:25:50 -06:00
parent 963f4d08bc
commit 95f1d47bb5
2 changed files with 44 additions and 1 deletions

View File

@ -30,9 +30,19 @@ from nova.consoleauth import rpcapi as consoleauth_rpcapi
from nova import context
from nova import exception
from nova.i18n import _
from oslo_config import cfg
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
console_origin_opts = [
cfg.ListOpt('console_allowed_origins',
default=[],
help='Allowed Origin header hostnames for access to console '
'proxy servers'),
]
CONF.register_opts(console_origin_opts)
class NovaProxyRequestHandlerBase(object):
def address_string(self):
@ -104,6 +114,8 @@ class NovaProxyRequestHandlerBase(object):
expected_origin_hostname = e.split(']')[0][1:]
else:
expected_origin_hostname = e.split(':')[0]
expected_origin_hostnames = CONF.console_allowed_origins
expected_origin_hostnames.append(expected_origin_hostname)
origin_url = self.headers.getheader('Origin')
# missing origin header indicates non-browser client which is OK
if origin_url is not None:
@ -113,7 +125,7 @@ class NovaProxyRequestHandlerBase(object):
if origin_hostname == '' or origin_scheme == '':
detail = _("Origin header not valid.")
raise exception.ValidationError(detail=detail)
if expected_origin_hostname != origin_hostname:
if origin_hostname not in expected_origin_hostnames:
detail = _("Origin header does not match this host.")
raise exception.ValidationError(detail=detail)
if not self.verify_origin_proto(connect_info, origin_scheme):

View File

@ -30,6 +30,8 @@ class NovaProxyRequestHandlerBaseTestCase(test.NoDBTestCase):
def setUp(self):
super(NovaProxyRequestHandlerBaseTestCase, self).setUp()
self.flags(console_allowed_origins = ['allowed-origin-example-1.net',
'allowed-origin-example-2.net'])
self.wh = websocketproxy.NovaProxyRequestHandlerBase()
self.wh.socket = mock.MagicMock()
self.wh.msg = mock.MagicMock()
@ -76,6 +78,16 @@ class NovaProxyRequestHandlerBaseTestCase(test.NoDBTestCase):
else:
return
def _fake_getheader_allowed_origin(self, header):
if header == 'cookie':
return 'token="123-456-789"'
elif header == 'Origin':
return 'https://allowed-origin-example-2.net:6080'
elif header == 'Host':
return 'example.net:6080'
else:
return
def _fake_getheader_blank_origin(self, header):
if header == 'cookie':
return 'token="123-456-789"'
@ -266,6 +278,25 @@ class NovaProxyRequestHandlerBaseTestCase(test.NoDBTestCase):
self.assertRaises(exception.ValidationError,
self.wh.new_websocket_client)
@mock.patch('nova.consoleauth.rpcapi.ConsoleAuthAPI.check_token')
def test_new_websocket_client_novnc_allowed_origin_header(self,
check_token):
check_token.return_value = {
'host': 'node1',
'port': '10000',
'console_type': 'novnc',
'access_url': 'https://example.net:6080'
}
self.wh.socket.return_value = '<socket>'
self.wh.path = "http://127.0.0.1/"
self.wh.headers.getheader = self._fake_getheader_allowed_origin
self.wh.new_websocket_client()
check_token.assert_called_with(mock.ANY, token="123-456-789")
self.wh.socket.assert_called_with('node1', 10000, connect=True)
self.wh.do_proxy.assert_called_with('<socket>')
@mock.patch('nova.consoleauth.rpcapi.ConsoleAuthAPI.check_token')
def test_new_websocket_client_novnc_blank_origin_header(self, check_token):
check_token.return_value = {