Browse Source

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
tags/14.0.4
Abhishek Raut 5 months ago
parent
commit
e252900cc0
7 changed files with 126 additions and 11 deletions
  1. +3
    -0
      vmware_nsxlib/tests/unit/v3/nsxlib_testcase.py
  2. +31
    -0
      vmware_nsxlib/tests/unit/v3/test_cluster.py
  3. +25
    -10
      vmware_nsxlib/v3/cluster.py
  4. +5
    -0
      vmware_nsxlib/v3/config.py
  5. +4
    -0
      vmware_nsxlib/v3/exceptions.py
  6. +16
    -1
      vmware_nsxlib/v3/lib.py
  7. +42
    -0
      vmware_nsxlib/v3/token_provider.py

+ 3
- 0
vmware_nsxlib/tests/unit/v3/nsxlib_testcase.py View File

@@ -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),

+ 31
- 0
vmware_nsxlib/tests/unit/v3/test_cluster.py View File

@@ -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 = {}

+ 25
- 10
vmware_nsxlib/v3/cluster.py View File

@@ -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.

+ 5
- 0
vmware_nsxlib/v3/config.py View File

@@ -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

+ 4
- 0
vmware_nsxlib/v3/exceptions.py View File

@@ -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")

+ 16
- 1
vmware_nsxlib/v3/lib.py View File

@@ -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
- 0
vmware_nsxlib/v3/token_provider.py View 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…
Cancel
Save