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
changes/93/679493/8
Abhishek Raut 1 month ago
parent
commit
e252900cc0

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

@@ -115,6 +115,7 @@ def get_default_nsxlib_config(allow_passthrough=True):
115 115
         password=NSX_PASSWORD,
116 116
         retries=NSX_HTTP_RETRIES,
117 117
         insecure=NSX_INSECURE,
118
+        token_provider=None,
118 119
         ca_file=NSX_CERT,
119 120
         concurrent_connections=NSX_CONCURENT_CONN,
120 121
         http_timeout=NSX_HTTP_TIMEOUT,
@@ -137,6 +138,7 @@ def get_nsxlib_config_with_client_cert():
137 138
         retries=NSX_HTTP_RETRIES,
138 139
         insecure=NSX_INSECURE,
139 140
         ca_file=NSX_CERT,
141
+        token_provider=None,
140 142
         concurrent_connections=NSX_CONCURENT_CONN,
141 143
         http_timeout=NSX_HTTP_TIMEOUT,
142 144
         http_read_timeout=NSX_HTTP_READ_TIMEOUT,
@@ -224,6 +226,7 @@ class NsxClientTestCase(NsxLibTestCase):
224 226
                 password=password or NSX_PASSWORD,
225 227
                 retries=retries or NSX_HTTP_RETRIES,
226 228
                 insecure=insecure if insecure is not None else NSX_INSECURE,
229
+                token_provider=None,
227 230
                 ca_file=ca_file or NSX_CERT,
228 231
                 concurrent_connections=(concurrent_connections or
229 232
                                         NSX_CONCURENT_CONN),

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

@@ -53,6 +53,7 @@ class RequestsHTTPProviderTestCase(unittest.TestCase):
53 53
         mock_api.nsxlib_config.password = 'nsxpassword'
54 54
         mock_api.nsxlib_config.retries = 100
55 55
         mock_api.nsxlib_config.insecure = True
56
+        mock_api.nsxlib_config.token_provider = None
56 57
         mock_api.nsxlib_config.ca_file = None
57 58
         mock_api.nsxlib_config.http_timeout = 99
58 59
         mock_api.nsxlib_config.conn_idle_timeout = 39
@@ -94,6 +95,36 @@ class RequestsHTTPProviderTestCase(unittest.TestCase):
94 95
             self.assertEqual(cert_provider_inst, session.cert_provider)
95 96
             self.assertEqual(99, session.timeout)
96 97
 
98
+    @mock.patch("vmware_nsxlib.v3.cluster.NSXRequestsHTTPProvider."
99
+                "get_default_headers")
100
+    def test_new_connection_with_token_provider(self, mock_get_def_headers):
101
+        mock_api = mock.Mock()
102
+        mock_api.nsxlib_config = mock.Mock()
103
+        mock_api.nsxlib_config.retries = 100
104
+        mock_api.nsxlib_config.insecure = True
105
+        mock_api.nsxlib_config.ca_file = None
106
+        mock_api.nsxlib_config.http_timeout = 99
107
+        mock_api.nsxlib_config.conn_idle_timeout = 39
108
+        mock_api.nsxlib_config.client_cert_provider = None
109
+        token_provider_inst = mock.Mock()
110
+        mock_api.nsxlib_config.token_provider = token_provider_inst
111
+        mock_api.nsxlib_config.allow_overwrite_header = False
112
+        provider = cluster.NSXRequestsHTTPProvider()
113
+        cluster_provider = cluster.Provider('9.8.7.6', 'https://9.8.7.6',
114
+                                            'nsxuser', 'nsxpassword', None)
115
+        with mock.patch.object(cluster.TimeoutSession, 'request',
116
+                               return_value=get_sess_create_resp()):
117
+            session = provider.new_connection(mock_api, cluster_provider)
118
+
119
+            self.assertIsNone(session.auth)
120
+            self.assertFalse(session.verify)
121
+            self.assertIsNone(session.cert)
122
+            self.assertEqual(100,
123
+                             session.adapters['https://'].max_retries.total)
124
+            self.assertEqual(99, session.timeout)
125
+            mock_get_def_headers.assert_called_once_with(
126
+                mock.ANY, cluster_provider, False, token_provider_inst)
127
+
97 128
     def test_validate_connection_keep_alive(self):
98 129
         mock_conn = mocks.MockRequestSessionApi()
99 130
         mock_conn.default_headers = {}

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

@@ -213,7 +213,9 @@ class NSXRequestsHTTPProvider(AbstractHTTPProvider):
213 213
                                  config.http_read_timeout)
214 214
         if config.client_cert_provider:
215 215
             session.cert_provider = config.client_cert_provider
216
-        else:
216
+        # Set the headers with Auth info when token provider is set,
217
+        # otherwise set the username and password
218
+        elif not config.token_provider:
217 219
             session.auth = (provider.username, provider.password)
218 220
 
219 221
         # NSX v3 doesn't use redirects
@@ -233,7 +235,8 @@ class NSXRequestsHTTPProvider(AbstractHTTPProvider):
233 235
         session.mount('https://', adapter)
234 236
 
235 237
         self.get_default_headers(session, provider,
236
-                                 config.allow_overwrite_header)
238
+                                 config.allow_overwrite_header,
239
+                                 config.token_provider)
237 240
 
238 241
         return session
239 242
 
@@ -246,22 +249,38 @@ class NSXRequestsHTTPProvider(AbstractHTTPProvider):
246 249
     def is_conn_open_exception(self, exception):
247 250
         return isinstance(exception, requests_exceptions.ConnectTimeout)
248 251
 
249
-    def get_default_headers(self, session, provider, allow_overwrite_header):
252
+    def get_default_headers(self, session, provider, allow_overwrite_header,
253
+                            token_provider=None):
250 254
         """Get the default headers that should be added to future requests"""
251 255
         session.default_headers = {}
252 256
 
257
+        # Add allow-overwrite if configured
258
+        if allow_overwrite_header:
259
+            session.default_headers['X-Allow-Overwrite'] = 'true'
253 260
         # Perform the initial session create and get the relevant jsessionid &
254 261
         # X-XSRF-TOKEN for future requests
255 262
         req_data = ''
256
-        if not session.cert_provider:
263
+        req_headers = {'Accept': 'application/json',
264
+                       'Content-Type': 'application/x-www-form-urlencoded'}
265
+        # Insert the JWT in Auth header if using tokens for auth
266
+        if token_provider:
267
+            try:
268
+                token_value = token_provider.get_token()
269
+                bearer_token = token_provider.get_header_value(token_value)
270
+                token_header = {"Authorization": bearer_token}
271
+                session.default_headers.update(token_header)
272
+                req_headers.update(token_header)
273
+            except exceptions.BadJSONWebTokenProviderRequest as e:
274
+                LOG.error("Session create failed for endpoint %s due to "
275
+                          "error in retrieving JSON Web Token: %s",
276
+                          provider.url, e)
277
+        elif not session.cert_provider:
257 278
             # With client certificate authentication, username and password
258 279
             # may not be provided.
259 280
             # If provided, backend treats these credentials as authentication
260 281
             # and ignores client cert as principal identity indication.
261 282
             req_data = 'j_username=%s&j_password=%s' % (provider.username,
262 283
                                                         provider.password)
263
-        req_headers = {'Accept': 'application/json',
264
-                       'Content-Type': 'application/x-www-form-urlencoded'}
265 284
         # Cannot use the certificate at this stage, because it is used for
266 285
         # the certificate generation
267 286
         try:
@@ -294,10 +313,6 @@ class NSXRequestsHTTPProvider(AbstractHTTPProvider):
294 313
                          "headers %(hdr)s",
295 314
                          {'url': provider.url, 'hdr': session.default_headers})
296 315
 
297
-        # Add allow-overwrite if configured
298
-        if allow_overwrite_header:
299
-            session.default_headers['X-Allow-Overwrite'] = 'true'
300
-
301 316
 
302 317
 class ClusterHealth(object):
303 318
     """Indicator of overall cluster health.

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

@@ -42,6 +42,9 @@ class NsxLibConfig(object):
42 42
                     "insecure" is set to True. If "insecure" is set to
43 43
                     False and ca_file is unset, the system root CAs will
44 44
                     be used to verify the server certificate.
45
+    :param token_provider: None, or instance of implemented AbstractJWTProvider
46
+                           which will return the JSON Web Token used in the
47
+                           requests in NSX for authorization.
45 48
 
46 49
     :param concurrent_connections: Maximum concurrent connections to each NSX
47 50
                                    manager.
@@ -95,6 +98,7 @@ class NsxLibConfig(object):
95 98
                  client_cert_provider=None,
96 99
                  insecure=True,
97 100
                  ca_file=None,
101
+                 token_provider=None,
98 102
                  concurrent_connections=10,
99 103
                  retries=3,
100 104
                  http_timeout=10,
@@ -127,6 +131,7 @@ class NsxLibConfig(object):
127 131
         self.conn_idle_timeout = conn_idle_timeout
128 132
         self.http_provider = http_provider
129 133
         self.client_cert_provider = client_cert_provider
134
+        self.token_provider = token_provider
130 135
         self.max_attempts = max_attempts
131 136
         self.plugin_scope = plugin_scope
132 137
         self.plugin_tag = plugin_tag

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

@@ -150,6 +150,10 @@ class BadXSRFToken(ManagerError):
150 150
     message = _("Bad or expired XSRF token")
151 151
 
152 152
 
153
+class BadJSONWebTokenProviderRequest(NsxLibException):
154
+    message = _("Bad or expired JSON web token request from provider: %(msg)s")
155
+
156
+
153 157
 class ServiceClusterUnavailable(ManagerError):
154 158
     message = _("Service cluster: '%(cluster_id)s' is unavailable. Please, "
155 159
                 "check NSX setup and/or configuration")

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

@@ -33,7 +33,9 @@ class NsxLibBase(object):
33 33
 
34 34
         self.nsx_version = None
35 35
         self.nsx_api = None
36
+        self.default_headers = None
36 37
         self.set_config(nsxlib_config)
38
+        self.set_default_headers(nsxlib_config)
37 39
 
38 40
         # create the Cluster
39 41
         self.cluster = cluster.NSXClusteredAPI(self.nsxlib_config)
@@ -44,7 +46,8 @@ class NsxLibBase(object):
44 46
             nsx_api_managers=self.nsxlib_config.nsx_api_managers,
45 47
             max_attempts=self.nsxlib_config.max_attempts,
46 48
             url_path_base=self.client_url_prefix,
47
-            rate_limit_retry=self.nsxlib_config.rate_limit_retry)
49
+            rate_limit_retry=self.nsxlib_config.rate_limit_retry,
50
+            default_headers=self.default_headers)
48 51
 
49 52
         self.general_apis = utils.NsxLibApiBase(
50 53
             self.client, self.nsxlib_config)
@@ -61,6 +64,18 @@ class NsxLibBase(object):
61 64
             validate_connection_method=self.validate_connection_method,
62 65
             url_base=self.client_url_prefix)
63 66
 
67
+    def set_default_headers(self, nsxlib_config):
68
+        """Set the default headers with token information"""
69
+        if nsxlib_config.token_provider:
70
+            try:
71
+                token_value = nsxlib_config.token_provider.get_token()
72
+            except exceptions.BadJSONWebTokenProviderRequest as e:
73
+                LOG.error("Error in retrieving JSON Web Token: %s", e)
74
+                return
75
+            bearer_token = "Bearer %s" % token_value
76
+            self.default_headers = self.default_headers or {}
77
+            self.default_headers["Authorization"] = bearer_token
78
+
64 79
     @abc.abstractproperty
65 80
     def client_url_prefix(self):
66 81
         pass

+ 42
- 0
vmware_nsxlib/v3/token_provider.py View File

@@ -0,0 +1,42 @@
1
+# Copyright 2019 VMware, Inc.
2
+# All Rights Reserved
3
+#
4
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
5
+#    not use this file except in compliance with the License. You may obtain
6
+#    a copy of the License at
7
+#
8
+#         http://www.apache.org/licenses/LICENSE-2.0
9
+#
10
+#    Unless required by applicable law or agreed to in writing, software
11
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+#    License for the specific language governing permissions and limitations
14
+#    under the License.
15
+
16
+import abc
17
+
18
+import six
19
+
20
+
21
+# NOTE: Consider inheriting from an abstract TokenProvider class to share
22
+#       interface with XSRF token
23
+@six.add_metaclass(abc.ABCMeta)
24
+class AbstractJWTProvider(object):
25
+    """Interface for providers of JSON Web Tokens(JWT)
26
+
27
+    Responsible to provide the token value and refresh it once expired,
28
+    or on demand, for authorization of requests to NSX.
29
+    """
30
+
31
+    @abc.abstractmethod
32
+    def get_token(self, refresh_token=False):
33
+        """Request JWT value.
34
+
35
+        :param refresh_token: Boolean value, indicating whether a new token
36
+                              value is to be retrieved.
37
+        :raises vmware_nsxlib.v3.exceptions.BadJSONWebTokenProviderRequest:
38
+        """
39
+        pass
40
+
41
+    def get_header_value(self, token_value):
42
+        return "Bearer %s" % token_value

Loading…
Cancel
Save