From 890e2d320e8e8e7b501af8e21cc6b751fb204d04 Mon Sep 17 00:00:00 2001 From: melanie witt Date: Wed, 16 Jan 2019 00:27:42 +0000 Subject: [PATCH] Use X-Forwarded-Proto as origin protocol if present When using a haproxy with SSL termination to provide secure console connections, haproxy will change the Origin header scheme to 'http' and add the 'X-Forwarded-Proto: https' header. This causes a failure in the Nova console proxy code which verifies that the Origin header scheme matches the access_url scheme for the connection, because the Origin header coming from haproxy is 'http' while the access_url scheme is 'https' or 'wss'. This looks for the X-Forwarded-Proto header and uses its scheme for the verification instead, if it is present. Closes-Bug: #1788180 Change-Id: I43401dc8368853654bf443273a0a1b5b9b63e3f0 --- nova/console/websocketproxy.py | 7 ++++ .../tests/unit/console/test_websocketproxy.py | 32 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/nova/console/websocketproxy.py b/nova/console/websocketproxy.py index 0463b7463886..6b692394942b 100644 --- a/nova/console/websocketproxy.py +++ b/nova/console/websocketproxy.py @@ -231,6 +231,13 @@ class NovaProxyRequestHandlerBase(object): origin = urlparse.urlparse(origin_url) origin_hostname = origin.hostname origin_scheme = origin.scheme + # If the console connection was forwarded by a proxy (example: + # haproxy), the original protocol could be contained in the + # X-Forwarded-Proto header instead of the Origin header. Prefer the + # forwarded protocol if it is present. + forwarded_proto = self.headers.get('X-Forwarded-Proto') + if forwarded_proto is not None: + origin_scheme = forwarded_proto if origin_hostname == '' or origin_scheme == '': detail = _("Origin header not valid.") raise exception.ValidationError(detail=detail) diff --git a/nova/tests/unit/console/test_websocketproxy.py b/nova/tests/unit/console/test_websocketproxy.py index d40e06b78a60..539f5c5f556c 100644 --- a/nova/tests/unit/console/test_websocketproxy.py +++ b/nova/tests/unit/console/test_websocketproxy.py @@ -628,6 +628,38 @@ class NovaProxyRequestHandlerBaseTestCase(test.NoDBTestCase): self.assertRaises(exception.ValidationError, self.wh.new_websocket_client) + @mock.patch('nova.console.websocketproxy.NovaProxyRequestHandlerBase.' + '_check_console_port') + @mock.patch('nova.objects.ConsoleAuthToken.validate') + def test_new_websocket_client_http_forwarded_proto_https(self, validate, + check_port): + params = { + 'id': 1, + 'token': '123-456-789', + 'instance_uuid': uuids.instance, + 'host': 'node1', + 'port': '10000', + 'console_type': 'serial', + 'access_url_base': 'wss://example.net:6080' + } + validate.return_value = objects.ConsoleAuthToken(**params) + + header = { + 'cookie': 'token="123-456-789"', + 'Origin': 'http://example.net:6080', + 'Host': 'example.net:6080', + 'X-Forwarded-Proto': 'https' + } + self.wh.socket.return_value = '' + self.wh.path = "https://127.0.0.1/" + self.wh.headers = header + + self.wh.new_websocket_client() + + validate.assert_called_with(mock.ANY, "123-456-789") + self.wh.socket.assert_called_with('node1', 10000, connect=True) + self.wh.do_proxy.assert_called_with('') + @mock.patch('nova.console.websocketproxy.NovaProxyRequestHandlerBase.' '_check_console_port') @mock.patch('nova.objects.ConsoleAuthToken.validate')