console: introduce framework for RFB authentication

Introduce a framework for providing RFB authentication scheme
implementations. This will be later used by the websocket RFB security
proxy code.

Change-Id: I98403ca922b83a460a4e7baa12bd5f596a79c940
Co-authored-by: Stephen Finucane <sfinucan@redhat.com>
Implements: bp websocket-proxy-to-host-security
This commit is contained in:
Stephen Finucane 2017-07-21 11:21:01 +01:00
parent 4d520e3cae
commit 3c7770f1af
10 changed files with 276 additions and 0 deletions

View File

@ -14,6 +14,7 @@
# under the License.
from oslo_config import cfg
from oslo_config import types
vnc_group = cfg.OptGroup(
'vnc',
@ -212,6 +213,24 @@ Related options:
* novncproxy_host
* novncproxy_base_url
"""),
cfg.ListOpt(
'auth_schemes',
item_type=types.String(
choices=['none']
),
default=['none'],
help="""
The authentication schemes to use with the compute node.
Control what RFB authentication schemes are permitted for connections between
the proxy and the compute host. If multiple schemes are enabled, the first
matching scheme will be used, thus the strongest schemes should be listed
first.
Possible values:
* "none": allow connection without authentication
"""),
]

View File

64
nova/console/rfb/auth.py Normal file
View File

@ -0,0 +1,64 @@
# Copyright (c) 2014-2017 Red Hat, Inc
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc
import enum
import six
VERSION_LENGTH = 12
AUTH_STATUS_FAIL = b"\x00"
AUTH_STATUS_PASS = b"\x01"
class AuthType(enum.IntEnum):
INVALID = 0
NONE = 1
VNC = 2
RA2 = 5
RA2NE = 6
TIGHT = 16
ULTRA = 17
TLS = 18 # Used by VINO
VENCRYPT = 19 # Used by VeNCrypt and QEMU
SASL = 20 # SASL type used by VINO and QEMU
ARD = 30 # Apple remote desktop (screen sharing)
MSLOGON = 0xfffffffa # Used by UltraVNC
@six.add_metaclass(abc.ABCMeta)
class RFBAuthScheme(object):
@abc.abstractmethod
def security_type(self):
"""Return the security type supported by this scheme
Returns the nova.console.rfb.auth.AuthType.XX
constant representing the scheme implemented.
"""
pass
@abc.abstractmethod
def security_handshake(self, compute_sock):
"""Perform security-type-specific functionality.
This method is expected to return the socket-like
object used to communicate with the server securely.
Should raise exception.RFBAuthHandshakeFailed if
an error occurs
"""
pass

View File

@ -0,0 +1,24 @@
# Copyright (c) 2014-2016 Red Hat, Inc
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from nova.console.rfb import auth
class RFBAuthSchemeNone(auth.RFBAuthScheme):
def security_type(self):
return auth.AuthType.NONE
def security_handshake(self, compute_sock):
return compute_sock

44
nova/console/rfb/auths.py Normal file
View File

@ -0,0 +1,44 @@
# Copyright (c) 2014-2017 Red Hat, Inc
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_config import cfg
from nova.console.rfb import authnone
from nova import exception
CONF = cfg.CONF
class RFBAuthSchemeList(object):
AUTH_SCHEME_MAP = {
"none": authnone.RFBAuthSchemeNone,
}
def __init__(self):
self.schemes = {}
for name in CONF.vnc.auth_schemes:
scheme = self.AUTH_SCHEME_MAP[name]()
self.schemes[scheme.security_type()] = scheme
def find_scheme(self, desired_types):
for security_type in desired_types:
if security_type in self.schemes:
return self.schemes[security_type]
raise exception.RFBAuthNoAvailableScheme(
allowed_types=", ".join([str(s) for s in self.schemes.keys()]),
desired_types=", ".join([str(s) for s in desired_types]))

View File

@ -1767,6 +1767,15 @@ class SecurityProxyNegotiationFailed(NovaException):
msg_fmt = _("Failed to negotiate security type with server: %(reason)s")
class RFBAuthHandshakeFailed(NovaException):
msg_fmt = _("Failed to complete auth handshake: %(reason)s")
class RFBAuthNoAvailableScheme(NovaException):
msg_fmt = _("No matching auth scheme: allowed types: '%(allowed_types)s', "
"desired types: '%(desired_types)s'")
class InvalidWatchdogAction(Invalid):
msg_fmt = _("Provided watchdog action (%(action)s) is not supported.")

View File

View File

@ -0,0 +1,72 @@
# Copyright (c) 2016 Red Hat, Inc
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from nova.console.rfb import auth
from nova.console.rfb import authnone
from nova.console.rfb import auths
from nova import exception
from nova import test
class RFBAuthSchemeListTestCase(test.NoDBTestCase):
def setUp(self):
super(RFBAuthSchemeListTestCase, self).setUp()
self.flags(auth_schemes=["none"], group="vnc")
def test_load_ok(self):
schemelist = auths.RFBAuthSchemeList()
security_types = sorted(schemelist.schemes.keys())
self.assertEqual(security_types, [auth.AuthType.NONE])
def test_load_unknown(self):
"""Ensure invalid auth schemes are not supported.
We're really testing oslo_policy functionality, but this case is
esoteric enough to warrant this.
"""
self.assertRaises(ValueError, self.flags,
auth_schemes=['none', 'wibble'], group='vnc')
def test_find_scheme_ok(self):
schemelist = auths.RFBAuthSchemeList()
scheme = schemelist.find_scheme(
[auth.AuthType.TIGHT,
auth.AuthType.NONE])
self.assertIsInstance(scheme, authnone.RFBAuthSchemeNone)
def test_find_scheme_fail(self):
schemelist = auths.RFBAuthSchemeList()
self.assertRaises(exception.RFBAuthNoAvailableScheme,
schemelist.find_scheme,
[auth.AuthType.TIGHT])
def test_find_scheme_priority(self):
schemelist = auths.RFBAuthSchemeList()
tight = mock.MagicMock(spec=auth.RFBAuthScheme)
schemelist.schemes[auth.AuthType.TIGHT] = tight
scheme = schemelist.find_scheme(
[auth.AuthType.TIGHT,
auth.AuthType.NONE])
self.assertEqual(tight, scheme)

View File

@ -0,0 +1,36 @@
# Copyright (c) 2014-2016 Red Hat, Inc
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from nova.console.rfb import auth
from nova.console.rfb import authnone
from nova import test
class RFBAuthSchemeNoneTestCase(test.NoDBTestCase):
def test_handshake(self):
scheme = authnone.RFBAuthSchemeNone()
sock = mock.MagicMock()
ret = scheme.security_handshake(sock)
self.assertEqual(sock, ret)
def test_types(self):
scheme = authnone.RFBAuthSchemeNone()
self.assertEqual(auth.AuthType.NONE, scheme.security_type())

View File

@ -0,0 +1,8 @@
---
features:
- |
Added a number of new configuration options to the ``[vnc]`` group, which
together allow for the configuration of authentication used between the
*nova-novncproxy* server and the compute node VNC server.
- ``auth_schemes``