Add JWT provider abstract class
Add JSON Web Token provider abstract class. In addition to this, allow clients to configure the token provider instance such when it is set, the Authorization header of NSXT requests has the bearer token value inserted. Change-Id: Ieb701411413ec239276685f02ee1364bd2b05abd
This commit is contained in:
parent
54393a235c
commit
e252900cc0
@ -115,6 +115,7 @@ def get_default_nsxlib_config(allow_passthrough=True):
|
||||
password=NSX_PASSWORD,
|
||||
retries=NSX_HTTP_RETRIES,
|
||||
insecure=NSX_INSECURE,
|
||||
token_provider=None,
|
||||
ca_file=NSX_CERT,
|
||||
concurrent_connections=NSX_CONCURENT_CONN,
|
||||
http_timeout=NSX_HTTP_TIMEOUT,
|
||||
@ -137,6 +138,7 @@ def get_nsxlib_config_with_client_cert():
|
||||
retries=NSX_HTTP_RETRIES,
|
||||
insecure=NSX_INSECURE,
|
||||
ca_file=NSX_CERT,
|
||||
token_provider=None,
|
||||
concurrent_connections=NSX_CONCURENT_CONN,
|
||||
http_timeout=NSX_HTTP_TIMEOUT,
|
||||
http_read_timeout=NSX_HTTP_READ_TIMEOUT,
|
||||
@ -224,6 +226,7 @@ class NsxClientTestCase(NsxLibTestCase):
|
||||
password=password or NSX_PASSWORD,
|
||||
retries=retries or NSX_HTTP_RETRIES,
|
||||
insecure=insecure if insecure is not None else NSX_INSECURE,
|
||||
token_provider=None,
|
||||
ca_file=ca_file or NSX_CERT,
|
||||
concurrent_connections=(concurrent_connections or
|
||||
NSX_CONCURENT_CONN),
|
||||
|
@ -53,6 +53,7 @@ class RequestsHTTPProviderTestCase(unittest.TestCase):
|
||||
mock_api.nsxlib_config.password = 'nsxpassword'
|
||||
mock_api.nsxlib_config.retries = 100
|
||||
mock_api.nsxlib_config.insecure = True
|
||||
mock_api.nsxlib_config.token_provider = None
|
||||
mock_api.nsxlib_config.ca_file = None
|
||||
mock_api.nsxlib_config.http_timeout = 99
|
||||
mock_api.nsxlib_config.conn_idle_timeout = 39
|
||||
@ -94,6 +95,36 @@ class RequestsHTTPProviderTestCase(unittest.TestCase):
|
||||
self.assertEqual(cert_provider_inst, session.cert_provider)
|
||||
self.assertEqual(99, session.timeout)
|
||||
|
||||
@mock.patch("vmware_nsxlib.v3.cluster.NSXRequestsHTTPProvider."
|
||||
"get_default_headers")
|
||||
def test_new_connection_with_token_provider(self, mock_get_def_headers):
|
||||
mock_api = mock.Mock()
|
||||
mock_api.nsxlib_config = mock.Mock()
|
||||
mock_api.nsxlib_config.retries = 100
|
||||
mock_api.nsxlib_config.insecure = True
|
||||
mock_api.nsxlib_config.ca_file = None
|
||||
mock_api.nsxlib_config.http_timeout = 99
|
||||
mock_api.nsxlib_config.conn_idle_timeout = 39
|
||||
mock_api.nsxlib_config.client_cert_provider = None
|
||||
token_provider_inst = mock.Mock()
|
||||
mock_api.nsxlib_config.token_provider = token_provider_inst
|
||||
mock_api.nsxlib_config.allow_overwrite_header = False
|
||||
provider = cluster.NSXRequestsHTTPProvider()
|
||||
cluster_provider = cluster.Provider('9.8.7.6', 'https://9.8.7.6',
|
||||
'nsxuser', 'nsxpassword', None)
|
||||
with mock.patch.object(cluster.TimeoutSession, 'request',
|
||||
return_value=get_sess_create_resp()):
|
||||
session = provider.new_connection(mock_api, cluster_provider)
|
||||
|
||||
self.assertIsNone(session.auth)
|
||||
self.assertFalse(session.verify)
|
||||
self.assertIsNone(session.cert)
|
||||
self.assertEqual(100,
|
||||
session.adapters['https://'].max_retries.total)
|
||||
self.assertEqual(99, session.timeout)
|
||||
mock_get_def_headers.assert_called_once_with(
|
||||
mock.ANY, cluster_provider, False, token_provider_inst)
|
||||
|
||||
def test_validate_connection_keep_alive(self):
|
||||
mock_conn = mocks.MockRequestSessionApi()
|
||||
mock_conn.default_headers = {}
|
||||
|
@ -213,7 +213,9 @@ class NSXRequestsHTTPProvider(AbstractHTTPProvider):
|
||||
config.http_read_timeout)
|
||||
if config.client_cert_provider:
|
||||
session.cert_provider = config.client_cert_provider
|
||||
else:
|
||||
# Set the headers with Auth info when token provider is set,
|
||||
# otherwise set the username and password
|
||||
elif not config.token_provider:
|
||||
session.auth = (provider.username, provider.password)
|
||||
|
||||
# NSX v3 doesn't use redirects
|
||||
@ -233,7 +235,8 @@ class NSXRequestsHTTPProvider(AbstractHTTPProvider):
|
||||
session.mount('https://', adapter)
|
||||
|
||||
self.get_default_headers(session, provider,
|
||||
config.allow_overwrite_header)
|
||||
config.allow_overwrite_header,
|
||||
config.token_provider)
|
||||
|
||||
return session
|
||||
|
||||
@ -246,22 +249,38 @@ class NSXRequestsHTTPProvider(AbstractHTTPProvider):
|
||||
def is_conn_open_exception(self, exception):
|
||||
return isinstance(exception, requests_exceptions.ConnectTimeout)
|
||||
|
||||
def get_default_headers(self, session, provider, allow_overwrite_header):
|
||||
def get_default_headers(self, session, provider, allow_overwrite_header,
|
||||
token_provider=None):
|
||||
"""Get the default headers that should be added to future requests"""
|
||||
session.default_headers = {}
|
||||
|
||||
# Add allow-overwrite if configured
|
||||
if allow_overwrite_header:
|
||||
session.default_headers['X-Allow-Overwrite'] = 'true'
|
||||
# Perform the initial session create and get the relevant jsessionid &
|
||||
# X-XSRF-TOKEN for future requests
|
||||
req_data = ''
|
||||
if not session.cert_provider:
|
||||
req_headers = {'Accept': 'application/json',
|
||||
'Content-Type': 'application/x-www-form-urlencoded'}
|
||||
# Insert the JWT in Auth header if using tokens for auth
|
||||
if token_provider:
|
||||
try:
|
||||
token_value = token_provider.get_token()
|
||||
bearer_token = token_provider.get_header_value(token_value)
|
||||
token_header = {"Authorization": bearer_token}
|
||||
session.default_headers.update(token_header)
|
||||
req_headers.update(token_header)
|
||||
except exceptions.BadJSONWebTokenProviderRequest as e:
|
||||
LOG.error("Session create failed for endpoint %s due to "
|
||||
"error in retrieving JSON Web Token: %s",
|
||||
provider.url, e)
|
||||
elif not session.cert_provider:
|
||||
# With client certificate authentication, username and password
|
||||
# may not be provided.
|
||||
# If provided, backend treats these credentials as authentication
|
||||
# and ignores client cert as principal identity indication.
|
||||
req_data = 'j_username=%s&j_password=%s' % (provider.username,
|
||||
provider.password)
|
||||
req_headers = {'Accept': 'application/json',
|
||||
'Content-Type': 'application/x-www-form-urlencoded'}
|
||||
# Cannot use the certificate at this stage, because it is used for
|
||||
# the certificate generation
|
||||
try:
|
||||
@ -294,10 +313,6 @@ class NSXRequestsHTTPProvider(AbstractHTTPProvider):
|
||||
"headers %(hdr)s",
|
||||
{'url': provider.url, 'hdr': session.default_headers})
|
||||
|
||||
# Add allow-overwrite if configured
|
||||
if allow_overwrite_header:
|
||||
session.default_headers['X-Allow-Overwrite'] = 'true'
|
||||
|
||||
|
||||
class ClusterHealth(object):
|
||||
"""Indicator of overall cluster health.
|
||||
|
@ -42,6 +42,9 @@ class NsxLibConfig(object):
|
||||
"insecure" is set to True. If "insecure" is set to
|
||||
False and ca_file is unset, the system root CAs will
|
||||
be used to verify the server certificate.
|
||||
:param token_provider: None, or instance of implemented AbstractJWTProvider
|
||||
which will return the JSON Web Token used in the
|
||||
requests in NSX for authorization.
|
||||
|
||||
:param concurrent_connections: Maximum concurrent connections to each NSX
|
||||
manager.
|
||||
@ -95,6 +98,7 @@ class NsxLibConfig(object):
|
||||
client_cert_provider=None,
|
||||
insecure=True,
|
||||
ca_file=None,
|
||||
token_provider=None,
|
||||
concurrent_connections=10,
|
||||
retries=3,
|
||||
http_timeout=10,
|
||||
@ -127,6 +131,7 @@ class NsxLibConfig(object):
|
||||
self.conn_idle_timeout = conn_idle_timeout
|
||||
self.http_provider = http_provider
|
||||
self.client_cert_provider = client_cert_provider
|
||||
self.token_provider = token_provider
|
||||
self.max_attempts = max_attempts
|
||||
self.plugin_scope = plugin_scope
|
||||
self.plugin_tag = plugin_tag
|
||||
|
@ -150,6 +150,10 @@ class BadXSRFToken(ManagerError):
|
||||
message = _("Bad or expired XSRF token")
|
||||
|
||||
|
||||
class BadJSONWebTokenProviderRequest(NsxLibException):
|
||||
message = _("Bad or expired JSON web token request from provider: %(msg)s")
|
||||
|
||||
|
||||
class ServiceClusterUnavailable(ManagerError):
|
||||
message = _("Service cluster: '%(cluster_id)s' is unavailable. Please, "
|
||||
"check NSX setup and/or configuration")
|
||||
|
@ -33,7 +33,9 @@ class NsxLibBase(object):
|
||||
|
||||
self.nsx_version = None
|
||||
self.nsx_api = None
|
||||
self.default_headers = None
|
||||
self.set_config(nsxlib_config)
|
||||
self.set_default_headers(nsxlib_config)
|
||||
|
||||
# create the Cluster
|
||||
self.cluster = cluster.NSXClusteredAPI(self.nsxlib_config)
|
||||
@ -44,7 +46,8 @@ class NsxLibBase(object):
|
||||
nsx_api_managers=self.nsxlib_config.nsx_api_managers,
|
||||
max_attempts=self.nsxlib_config.max_attempts,
|
||||
url_path_base=self.client_url_prefix,
|
||||
rate_limit_retry=self.nsxlib_config.rate_limit_retry)
|
||||
rate_limit_retry=self.nsxlib_config.rate_limit_retry,
|
||||
default_headers=self.default_headers)
|
||||
|
||||
self.general_apis = utils.NsxLibApiBase(
|
||||
self.client, self.nsxlib_config)
|
||||
@ -61,6 +64,18 @@ class NsxLibBase(object):
|
||||
validate_connection_method=self.validate_connection_method,
|
||||
url_base=self.client_url_prefix)
|
||||
|
||||
def set_default_headers(self, nsxlib_config):
|
||||
"""Set the default headers with token information"""
|
||||
if nsxlib_config.token_provider:
|
||||
try:
|
||||
token_value = nsxlib_config.token_provider.get_token()
|
||||
except exceptions.BadJSONWebTokenProviderRequest as e:
|
||||
LOG.error("Error in retrieving JSON Web Token: %s", e)
|
||||
return
|
||||
bearer_token = "Bearer %s" % token_value
|
||||
self.default_headers = self.default_headers or {}
|
||||
self.default_headers["Authorization"] = bearer_token
|
||||
|
||||
@abc.abstractproperty
|
||||
def client_url_prefix(self):
|
||||
pass
|
||||
|
42
vmware_nsxlib/v3/token_provider.py
Normal file
42
vmware_nsxlib/v3/token_provider.py
Normal file
@ -0,0 +1,42 @@
|
||||
# Copyright 2019 VMware, 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 abc
|
||||
|
||||
import six
|
||||
|
||||
|
||||
# NOTE: Consider inheriting from an abstract TokenProvider class to share
|
||||
# interface with XSRF token
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class AbstractJWTProvider(object):
|
||||
"""Interface for providers of JSON Web Tokens(JWT)
|
||||
|
||||
Responsible to provide the token value and refresh it once expired,
|
||||
or on demand, for authorization of requests to NSX.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_token(self, refresh_token=False):
|
||||
"""Request JWT value.
|
||||
|
||||
:param refresh_token: Boolean value, indicating whether a new token
|
||||
value is to be retrieved.
|
||||
:raises vmware_nsxlib.v3.exceptions.BadJSONWebTokenProviderRequest:
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_header_value(self, token_value):
|
||||
return "Bearer %s" % token_value
|
Loading…
x
Reference in New Issue
Block a user