enforce remote console shutdown
- Adds a CONF option enforce_session_timeout - Adds Timer to close connection once token expire - refactor close_connection functionality - Fixes existing and adds new unit tests - Adds release note - Updates admin guide Change-Id: I5d7e8faf1d271e9dd98d24e825631246308e7141
This commit is contained in:
@@ -69,6 +69,31 @@ This particular example is illustrated below.
|
|||||||
:alt: noVNC process
|
:alt: noVNC process
|
||||||
:width: 95%
|
:width: 95%
|
||||||
|
|
||||||
|
Consoleauth configuration:
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
The consoleauth accepts following options:
|
||||||
|
|
||||||
|
- :oslo.config:option:`consoleauth.token_ttl`
|
||||||
|
- :oslo.config:option:`consoleauth.enforce_session_timeout`
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[consoleauth]
|
||||||
|
token_ttl = 1000 # default value is 600 second
|
||||||
|
enforce_session_timeout = True # default is False
|
||||||
|
|
||||||
|
|
||||||
|
Supported consoles:
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
* :ref:`vnc-console`
|
||||||
|
* :ref:`spice-console`
|
||||||
|
* :ref:`serial-console`
|
||||||
|
* :ref:`mks-console`
|
||||||
|
|
||||||
|
|
||||||
|
.. _vnc-console:
|
||||||
|
|
||||||
noVNC-based VNC console
|
noVNC-based VNC console
|
||||||
-----------------------
|
-----------------------
|
||||||
@@ -294,6 +319,8 @@ be told where to find them. This requires editing :file:`nova.conf` to set.
|
|||||||
vencrypt_ca_certs=/etc/pki/nova-novncproxy/ca-cert.pem
|
vencrypt_ca_certs=/etc/pki/nova-novncproxy/ca-cert.pem
|
||||||
|
|
||||||
|
|
||||||
|
.. _spice-console:
|
||||||
|
|
||||||
SPICE console
|
SPICE console
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
@@ -376,9 +403,10 @@ for SPICE consoles.
|
|||||||
- :oslo.config:option:`spice.playback_compression`
|
- :oslo.config:option:`spice.playback_compression`
|
||||||
- :oslo.config:option:`spice.streaming_mode`
|
- :oslo.config:option:`spice.streaming_mode`
|
||||||
|
|
||||||
|
.. _serial-console:
|
||||||
|
|
||||||
Serial
|
Serial console
|
||||||
------
|
--------------
|
||||||
|
|
||||||
Serial consoles provide an alternative to graphical consoles like VNC or SPICE.
|
Serial consoles provide an alternative to graphical consoles like VNC or SPICE.
|
||||||
They work a little differently to graphical consoles so an example is
|
They work a little differently to graphical consoles so an example is
|
||||||
@@ -468,9 +496,10 @@ There are some things to keep in mind when configuring these options:
|
|||||||
:program:`nova-serialproxy` service to determine where to connect to for
|
:program:`nova-serialproxy` service to determine where to connect to for
|
||||||
proxying the console interaction.
|
proxying the console interaction.
|
||||||
|
|
||||||
|
.. _mks-console:
|
||||||
|
|
||||||
MKS
|
MKS console
|
||||||
---
|
-----------
|
||||||
|
|
||||||
MKS is the protocol used for accessing the console of a virtual machine running
|
MKS is the protocol used for accessing the console of a virtual machine running
|
||||||
on VMware vSphere. It is very similar to VNC. Due to the architecture of the
|
on VMware vSphere. It is very similar to VNC. Due to the architecture of the
|
||||||
@@ -576,6 +605,13 @@ Frequently Asked Questions
|
|||||||
console connections, make sure that the value of ``novncproxy_base_url`` is
|
console connections, make sure that the value of ``novncproxy_base_url`` is
|
||||||
set explicitly where the ``nova-novncproxy`` service is running.
|
set explicitly where the ``nova-novncproxy`` service is running.
|
||||||
|
|
||||||
|
- **Q: How do I know which nova config file to update to set a particular config option?**
|
||||||
|
|
||||||
|
A: First, find out which nova-service is responsible for the change you want
|
||||||
|
to make, using ``ps -aux | grep nova``. Once the service is found, check the
|
||||||
|
status of the service via systemctl. In the status output, associated conf
|
||||||
|
files with respective paths will be listed.
|
||||||
|
|
||||||
|
|
||||||
References
|
References
|
||||||
----------
|
----------
|
||||||
|
|||||||
@@ -32,7 +32,18 @@ The lifetime of a console auth token (in seconds).
|
|||||||
A console auth token is used in authorizing console access for a user.
|
A console auth token is used in authorizing console access for a user.
|
||||||
Once the auth token time to live count has elapsed, the token is
|
Once the auth token time to live count has elapsed, the token is
|
||||||
considered expired. Expired tokens are then deleted.
|
considered expired. Expired tokens are then deleted.
|
||||||
""")
|
"""),
|
||||||
|
cfg.BoolOpt(
|
||||||
|
'enforce_session_timeout',
|
||||||
|
default=False,
|
||||||
|
help="""
|
||||||
|
Enable or disable enforce session timeout for VM console.
|
||||||
|
|
||||||
|
This allows operators to enforce a console session timeout.
|
||||||
|
When set to True, Nova will automatically close the console session
|
||||||
|
at the server end once token_ttl expires, providing enhanced
|
||||||
|
control over console session duration.
|
||||||
|
"""),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ from nova import exception
|
|||||||
from nova.i18n import _
|
from nova.i18n import _
|
||||||
from nova import objects
|
from nova import objects
|
||||||
|
|
||||||
|
from oslo_utils import timeutils
|
||||||
|
import threading
|
||||||
|
|
||||||
# Location of WebSockifyServer class in websockify v0.9.0
|
# Location of WebSockifyServer class in websockify v0.9.0
|
||||||
websockifyserver = importutils.try_import('websockify.websockifyserver')
|
websockifyserver = importutils.try_import('websockify.websockifyserver')
|
||||||
|
|
||||||
@@ -147,6 +150,20 @@ class NovaProxyRequestHandler(websockify.ProxyRequestHandler):
|
|||||||
|
|
||||||
return connect_info
|
return connect_info
|
||||||
|
|
||||||
|
def _close_connection(self, tsock, host, port):
|
||||||
|
"""takes target socket and close the connection.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
tsock.shutdown(socket.SHUT_RDWR)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
if tsock.fileno() != -1:
|
||||||
|
tsock.close()
|
||||||
|
self.vmsg(_("%(host)s:%(port)s: "
|
||||||
|
"Websocket client or target closed") %
|
||||||
|
{'host': host, 'port': port})
|
||||||
|
|
||||||
def new_websocket_client(self):
|
def new_websocket_client(self):
|
||||||
"""Called after a new WebSocket connection has been established."""
|
"""Called after a new WebSocket connection has been established."""
|
||||||
# Reopen the eventlet hub to make sure we don't share an epoll
|
# Reopen the eventlet hub to make sure we don't share an epoll
|
||||||
@@ -260,14 +277,15 @@ class NovaProxyRequestHandler(websockify.ProxyRequestHandler):
|
|||||||
|
|
||||||
# Start proxying
|
# Start proxying
|
||||||
try:
|
try:
|
||||||
|
if CONF.consoleauth.enforce_session_timeout:
|
||||||
|
conn_timeout = connect_info.expires - timeutils.utcnow_ts()
|
||||||
|
LOG.info('%s seconds to terminate connection.', conn_timeout)
|
||||||
|
threading.Timer(conn_timeout, self._close_connection,
|
||||||
|
[tsock, host, port]).start()
|
||||||
|
|
||||||
self.do_proxy(tsock)
|
self.do_proxy(tsock)
|
||||||
except Exception:
|
except Exception:
|
||||||
if tsock:
|
self._close_connection(tsock, host, port)
|
||||||
tsock.shutdown(socket.SHUT_RDWR)
|
|
||||||
tsock.close()
|
|
||||||
self.vmsg(_("%(host)s:%(port)s: "
|
|
||||||
"Websocket client or target closed") %
|
|
||||||
{'host': host, 'port': port})
|
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def socket(self, *args, **kwargs):
|
def socket(self, *args, **kwargs):
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ from oslo_log import log as logging
|
|||||||
from oslo_utils import strutils
|
from oslo_utils import strutils
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
|
from oslo_utils import versionutils
|
||||||
|
|
||||||
from nova.db.main import api as db
|
from nova.db.main import api as db
|
||||||
from nova import exception
|
from nova import exception
|
||||||
@@ -38,7 +39,9 @@ class ConsoleAuthToken(base.NovaTimestampObject, base.NovaObject):
|
|||||||
# Version 1.1: Add clean_expired_console_auths method.
|
# Version 1.1: Add clean_expired_console_auths method.
|
||||||
# The clean_expired_console_auths_for_host method
|
# The clean_expired_console_auths_for_host method
|
||||||
# was deprecated.
|
# was deprecated.
|
||||||
VERSION = '1.1'
|
# Version 1.2: Add expires field.
|
||||||
|
# This is to see token expire time.
|
||||||
|
VERSION = '1.2'
|
||||||
|
|
||||||
fields = {
|
fields = {
|
||||||
'id': fields.IntegerField(),
|
'id': fields.IntegerField(),
|
||||||
@@ -52,6 +55,7 @@ class ConsoleAuthToken(base.NovaTimestampObject, base.NovaObject):
|
|||||||
# database. A hash of the token is stored instead and is not a
|
# database. A hash of the token is stored instead and is not a
|
||||||
# field on the object.
|
# field on the object.
|
||||||
'token': fields.StringField(nullable=False),
|
'token': fields.StringField(nullable=False),
|
||||||
|
'expires': fields.IntegerField(nullable=False),
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -77,6 +81,12 @@ class ConsoleAuthToken(base.NovaTimestampObject, base.NovaObject):
|
|||||||
else:
|
else:
|
||||||
return '%s?token=%s' % (self.access_url_base, self.token)
|
return '%s?token=%s' % (self.access_url_base, self.token)
|
||||||
|
|
||||||
|
def obj_make_compatible(self, primitive, target_version):
|
||||||
|
super().obj_make_compatible(primitive, target_version)
|
||||||
|
target_version = versionutils.convert_version_to_tuple(target_version)
|
||||||
|
if target_version < (1, 2) and 'expires' in primitive:
|
||||||
|
primitive.pop('expires', None)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _from_db_object(context, obj, db_obj):
|
def _from_db_object(context, obj, db_obj):
|
||||||
# NOTE(PaulMurray): token is not stored in the database but
|
# NOTE(PaulMurray): token is not stored in the database but
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"""Tests for nova websocketproxy."""
|
"""Tests for nova websocketproxy."""
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import fixtures
|
||||||
import io
|
import io
|
||||||
import socket
|
import socket
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
@@ -143,6 +144,9 @@ class NovaProxyRequestHandlerTestCase(test.NoDBTestCase):
|
|||||||
self.wh.do_proxy = mock.MagicMock()
|
self.wh.do_proxy = mock.MagicMock()
|
||||||
self.wh.headers = mock.MagicMock()
|
self.wh.headers = mock.MagicMock()
|
||||||
|
|
||||||
|
self.threading_timer_mock = self.useFixture(
|
||||||
|
fixtures.MockPatch('threading.Timer', mock.DEFAULT)).mock
|
||||||
|
|
||||||
fake_header = {
|
fake_header = {
|
||||||
'cookie': 'token="123-456-789"',
|
'cookie': 'token="123-456-789"',
|
||||||
'Origin': 'https://example.net:6080',
|
'Origin': 'https://example.net:6080',
|
||||||
@@ -207,6 +211,7 @@ class NovaProxyRequestHandlerTestCase(test.NoDBTestCase):
|
|||||||
'host': 'node1',
|
'host': 'node1',
|
||||||
'port': '10000',
|
'port': '10000',
|
||||||
'console_type': 'novnc',
|
'console_type': 'novnc',
|
||||||
|
'expires': '100',
|
||||||
'access_url_base': 'https://example.net:6080'
|
'access_url_base': 'https://example.net:6080'
|
||||||
}
|
}
|
||||||
validate.return_value = objects.ConsoleAuthToken(**params)
|
validate.return_value = objects.ConsoleAuthToken(**params)
|
||||||
@@ -235,11 +240,13 @@ class NovaProxyRequestHandlerTestCase(test.NoDBTestCase):
|
|||||||
'host': 'node1',
|
'host': 'node1',
|
||||||
'port': '10000',
|
'port': '10000',
|
||||||
'console_type': 'novnc',
|
'console_type': 'novnc',
|
||||||
|
'expires': '100',
|
||||||
'access_url_base': 'https://[2001:db8::1]:6080'
|
'access_url_base': 'https://[2001:db8::1]:6080'
|
||||||
}
|
}
|
||||||
validate.return_value = objects.ConsoleAuthToken(**params)
|
validate.return_value = objects.ConsoleAuthToken(**params)
|
||||||
|
|
||||||
self.wh.socket.return_value = '<socket>'
|
tsock = mock.MagicMock()
|
||||||
|
self.wh.socket.return_value = tsock
|
||||||
self.wh.path = "http://[2001:db8::1]/?token=123-456-789"
|
self.wh.path = "http://[2001:db8::1]/?token=123-456-789"
|
||||||
self.wh.headers = self.fake_header_ipv6
|
self.wh.headers = self.fake_header_ipv6
|
||||||
|
|
||||||
@@ -247,7 +254,7 @@ class NovaProxyRequestHandlerTestCase(test.NoDBTestCase):
|
|||||||
|
|
||||||
validate.assert_called_with(mock.ANY, "123-456-789")
|
validate.assert_called_with(mock.ANY, "123-456-789")
|
||||||
self.wh.socket.assert_called_with('node1', 10000, connect=True)
|
self.wh.socket.assert_called_with('node1', 10000, connect=True)
|
||||||
self.wh.do_proxy.assert_called_with('<socket>')
|
self.wh.do_proxy.assert_called_with(tsock)
|
||||||
|
|
||||||
@mock.patch('nova.objects.ConsoleAuthToken.validate')
|
@mock.patch('nova.objects.ConsoleAuthToken.validate')
|
||||||
def test_new_websocket_client_token_invalid(self, validate):
|
def test_new_websocket_client_token_invalid(self, validate):
|
||||||
@@ -273,6 +280,7 @@ class NovaProxyRequestHandlerTestCase(test.NoDBTestCase):
|
|||||||
'port': '10000',
|
'port': '10000',
|
||||||
'internal_access_path': 'vmid',
|
'internal_access_path': 'vmid',
|
||||||
'console_type': 'novnc',
|
'console_type': 'novnc',
|
||||||
|
'expires': '100',
|
||||||
'access_url_base': 'https://example.net:6080'
|
'access_url_base': 'https://example.net:6080'
|
||||||
}
|
}
|
||||||
validate.return_value = objects.ConsoleAuthToken(**params)
|
validate.return_value = objects.ConsoleAuthToken(**params)
|
||||||
@@ -304,6 +312,7 @@ class NovaProxyRequestHandlerTestCase(test.NoDBTestCase):
|
|||||||
'port': '10000',
|
'port': '10000',
|
||||||
'internal_access_path': 'xxx',
|
'internal_access_path': 'xxx',
|
||||||
'console_type': 'novnc',
|
'console_type': 'novnc',
|
||||||
|
'expires': '100',
|
||||||
'access_url_base': 'https://example.net:6080'
|
'access_url_base': 'https://example.net:6080'
|
||||||
}
|
}
|
||||||
validate.return_value = objects.ConsoleAuthToken(**params)
|
validate.return_value = objects.ConsoleAuthToken(**params)
|
||||||
@@ -409,7 +418,8 @@ class NovaProxyRequestHandlerTestCase(test.NoDBTestCase):
|
|||||||
}
|
}
|
||||||
validate.return_value = objects.ConsoleAuthToken(**params)
|
validate.return_value = objects.ConsoleAuthToken(**params)
|
||||||
|
|
||||||
self.wh.socket.return_value = '<socket>'
|
tsock = mock.MagicMock()
|
||||||
|
self.wh.socket.return_value = tsock
|
||||||
self.wh.path = "http://127.0.0.1/"
|
self.wh.path = "http://127.0.0.1/"
|
||||||
self.wh.headers = self.fake_header_allowed_origin
|
self.wh.headers = self.fake_header_allowed_origin
|
||||||
|
|
||||||
@@ -417,7 +427,7 @@ class NovaProxyRequestHandlerTestCase(test.NoDBTestCase):
|
|||||||
|
|
||||||
validate.assert_called_with(mock.ANY, "123-456-789")
|
validate.assert_called_with(mock.ANY, "123-456-789")
|
||||||
self.wh.socket.assert_called_with('node1', 10000, connect=True)
|
self.wh.socket.assert_called_with('node1', 10000, connect=True)
|
||||||
self.wh.do_proxy.assert_called_with('<socket>')
|
self.wh.do_proxy.assert_called_with(tsock)
|
||||||
|
|
||||||
@mock.patch('nova.console.websocketproxy.NovaProxyRequestHandler.'
|
@mock.patch('nova.console.websocketproxy.NovaProxyRequestHandler.'
|
||||||
'_check_console_port')
|
'_check_console_port')
|
||||||
@@ -455,7 +465,9 @@ class NovaProxyRequestHandlerTestCase(test.NoDBTestCase):
|
|||||||
}
|
}
|
||||||
validate.return_value = objects.ConsoleAuthToken(**params)
|
validate.return_value = objects.ConsoleAuthToken(**params)
|
||||||
|
|
||||||
self.wh.socket.return_value = '<socket>'
|
tsock = mock.MagicMock()
|
||||||
|
self.wh.socket.return_value = tsock
|
||||||
|
|
||||||
self.wh.path = "http://127.0.0.1/"
|
self.wh.path = "http://127.0.0.1/"
|
||||||
self.wh.headers = self.fake_header_no_origin
|
self.wh.headers = self.fake_header_no_origin
|
||||||
|
|
||||||
@@ -463,7 +475,7 @@ class NovaProxyRequestHandlerTestCase(test.NoDBTestCase):
|
|||||||
|
|
||||||
validate.assert_called_with(mock.ANY, "123-456-789")
|
validate.assert_called_with(mock.ANY, "123-456-789")
|
||||||
self.wh.socket.assert_called_with('node1', 10000, connect=True)
|
self.wh.socket.assert_called_with('node1', 10000, connect=True)
|
||||||
self.wh.do_proxy.assert_called_with('<socket>')
|
self.wh.do_proxy.assert_called_with(tsock)
|
||||||
|
|
||||||
@mock.patch('nova.console.websocketproxy.NovaProxyRequestHandler.'
|
@mock.patch('nova.console.websocketproxy.NovaProxyRequestHandler.'
|
||||||
'_check_console_port')
|
'_check_console_port')
|
||||||
@@ -694,6 +706,85 @@ class NovaProxyRequestHandlerTestCase(test.NoDBTestCase):
|
|||||||
websocketproxy.NovaWebSocketProxy(ssl_minimum_version=minver)
|
websocketproxy.NovaWebSocketProxy(ssl_minimum_version=minver)
|
||||||
mock_select_ssl.assert_called_once_with(minver)
|
mock_select_ssl.assert_called_once_with(minver)
|
||||||
|
|
||||||
|
@mock.patch('nova.console.websocketproxy.NovaProxyRequestHandler.'
|
||||||
|
'_check_console_port')
|
||||||
|
@mock.patch('nova.objects.ConsoleAuthToken.validate')
|
||||||
|
def test_enforce_session_timeout_timer_called(
|
||||||
|
self, validate, check_port):
|
||||||
|
params = {
|
||||||
|
'id': 1,
|
||||||
|
'token': '123-456-789',
|
||||||
|
'instance_uuid': uuids.instance,
|
||||||
|
'host': 'node1',
|
||||||
|
'port': '10000',
|
||||||
|
'console_type': 'novnc',
|
||||||
|
'expires': '100',
|
||||||
|
'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
|
||||||
|
|
||||||
|
# in CONF, set to enforce session timeout
|
||||||
|
self.flags(enforce_session_timeout=True, group='consoleauth')
|
||||||
|
self.wh.new_websocket_client()
|
||||||
|
self.threading_timer_mock.assert_called_once()
|
||||||
|
|
||||||
|
@mock.patch('nova.console.websocketproxy.NovaProxyRequestHandler.'
|
||||||
|
'_check_console_port')
|
||||||
|
@mock.patch('nova.objects.ConsoleAuthToken.validate')
|
||||||
|
def test_enforce_session_timeout_timer_not_called(
|
||||||
|
self, validate, check_port):
|
||||||
|
params = {
|
||||||
|
'id': 1,
|
||||||
|
'token': '123-456-789',
|
||||||
|
'instance_uuid': uuids.instance,
|
||||||
|
'host': 'node1',
|
||||||
|
'port': '10000',
|
||||||
|
'console_type': 'novnc',
|
||||||
|
'expires': '100',
|
||||||
|
'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
|
||||||
|
|
||||||
|
self.flags(enforce_session_timeout=False, group='consoleauth')
|
||||||
|
self.wh.new_websocket_client()
|
||||||
|
self.threading_timer_mock.assert_not_called()
|
||||||
|
|
||||||
|
def test__close_connection(self):
|
||||||
|
tsock = mock.MagicMock()
|
||||||
|
self.wh.vmsg = mock.MagicMock()
|
||||||
|
host = 'node1'
|
||||||
|
port = '10000'
|
||||||
|
|
||||||
|
self.wh._close_connection(tsock, host, port)
|
||||||
|
tsock.shutdown.assert_called_once_with(socket.SHUT_RDWR)
|
||||||
|
tsock.close.assert_called_once()
|
||||||
|
self.wh.vmsg.assert_called_once_with(
|
||||||
|
f"{host}:{port}: Websocket client or target closed")
|
||||||
|
|
||||||
|
def test__close_connection_raise_OSError(self):
|
||||||
|
tsock = mock.MagicMock()
|
||||||
|
self.wh.vmsg = mock.MagicMock()
|
||||||
|
host = 'node1'
|
||||||
|
port = '10000'
|
||||||
|
|
||||||
|
tsock.shutdown.side_effect = OSError("Error")
|
||||||
|
|
||||||
|
self.wh._close_connection(tsock, host, port)
|
||||||
|
|
||||||
|
tsock.shutdown.assert_called_once_with(socket.SHUT_RDWR)
|
||||||
|
tsock.close.assert_called_once()
|
||||||
|
|
||||||
|
self.wh.vmsg.assert_called_once_with(
|
||||||
|
f"{host}:{port}: Websocket client or target closed")
|
||||||
|
|
||||||
|
|
||||||
class NovaWebsocketSecurityProxyTestCase(test.NoDBTestCase):
|
class NovaWebsocketSecurityProxyTestCase(test.NoDBTestCase):
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class _TestConsoleAuthToken(object):
|
|||||||
|
|
||||||
expected = copy.deepcopy(fakes.fake_token_dict)
|
expected = copy.deepcopy(fakes.fake_token_dict)
|
||||||
del expected['token_hash']
|
del expected['token_hash']
|
||||||
del expected['expires']
|
expected['expires'] = expires
|
||||||
expected['token'] = fakes.fake_token
|
expected['token'] = fakes.fake_token
|
||||||
expected['console_type'] = console_type
|
expected['console_type'] = console_type
|
||||||
|
|
||||||
@@ -85,6 +85,14 @@ class _TestConsoleAuthToken(object):
|
|||||||
path)
|
path)
|
||||||
self.assertEqual(expected_url, url)
|
self.assertEqual(expected_url, url)
|
||||||
|
|
||||||
|
# verify auth_token 'expires' backward version compatibility
|
||||||
|
data = lambda x: x['nova_object.data']
|
||||||
|
console_auth_obj_primitive = data(obj.obj_to_primitive())
|
||||||
|
self.assertIn('expires', console_auth_obj_primitive)
|
||||||
|
obj.obj_make_compatible(console_auth_obj_primitive, '1.1')
|
||||||
|
self.assertIn('token', console_auth_obj_primitive)
|
||||||
|
self.assertNotIn('expires', console_auth_obj_primitive)
|
||||||
|
|
||||||
def test_authorize(self):
|
def test_authorize(self):
|
||||||
self._test_authorize(fakes.fake_token_dict['console_type'])
|
self._test_authorize(fakes.fake_token_dict['console_type'])
|
||||||
|
|
||||||
|
|||||||
@@ -1087,7 +1087,7 @@ object_data = {
|
|||||||
'CellMappingList': '1.1-496ef79bb2ab41041fff8bcb57996352',
|
'CellMappingList': '1.1-496ef79bb2ab41041fff8bcb57996352',
|
||||||
'ComputeNode': '1.19-af6bd29a6c3b225da436a0d8487096f2',
|
'ComputeNode': '1.19-af6bd29a6c3b225da436a0d8487096f2',
|
||||||
'ComputeNodeList': '1.17-52f3b0962b1c86b98590144463ebb192',
|
'ComputeNodeList': '1.17-52f3b0962b1c86b98590144463ebb192',
|
||||||
'ConsoleAuthToken': '1.1-8da320fb065080eb4d3c2e5c59f8bf52',
|
'ConsoleAuthToken': '1.2-a4677c3576ad91eb02068a5cb9d38eaa',
|
||||||
'CpuDiagnostics': '1.0-d256f2e442d1b837735fd17dfe8e3d47',
|
'CpuDiagnostics': '1.0-d256f2e442d1b837735fd17dfe8e3d47',
|
||||||
'Destination': '1.4-3b440d29459e2c98987ad5b25ad1cb2c',
|
'Destination': '1.4-3b440d29459e2c98987ad5b25ad1cb2c',
|
||||||
'DeviceBus': '1.0-77509ea1ea0dd750d5864b9bd87d3f9d',
|
'DeviceBus': '1.0-77509ea1ea0dd750d5864b9bd87d3f9d',
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
This is a security-enhancing feature that automatically closes console sessions
|
||||||
|
exceeding a defined timeout period. To enable this functionality, operators are
|
||||||
|
required to set the 'enforce_session_timeout' boolean configuration option to True.
|
||||||
|
|
||||||
|
The enforcement is implemented via a timer mechanism, initiating when users access
|
||||||
|
the console and concluding upon the expiration of the set console token.
|
||||||
|
|
||||||
|
This ensures the graceful closure of console sessions on the server side, aligning
|
||||||
|
with security best practices.
|
||||||
Reference in New Issue
Block a user