From 24a6b02afae21010e4bd982f957ae4dd5958db1d Mon Sep 17 00:00:00 2001
From: Norbert Biczo <pyrooka@users.noreply.github.com>
Date: Mon, 25 Oct 2021 18:17:56 +0200
Subject: [PATCH 1/8] feat(VPCInstanceAuthenticator): add support for VPC
 authentication flow

---
 Authentication.md                             |  98 +++++++++++-
 ibm_cloud_sdk_core/__init__.py                |   1 +
 ibm_cloud_sdk_core/authenticators/__init__.py |   1 +
 .../authenticators/authenticator.py           |   1 +
 .../vpc_instance_authenticator.py             | 120 ++++++++++++++
 ibm_cloud_sdk_core/get_authenticator.py       |   6 +
 .../vpc_instance_token_manager.py             | 150 ++++++++++++++++++
 7 files changed, 371 insertions(+), 6 deletions(-)
 create mode 100644 ibm_cloud_sdk_core/authenticators/vpc_instance_authenticator.py
 create mode 100644 ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py

diff --git a/Authentication.md b/Authentication.md
index d1c4d557..da4e3be0 100644
--- a/Authentication.md
+++ b/Authentication.md
@@ -3,6 +3,7 @@ The python-sdk-core project supports the following types of authentication:
 - Basic Authentication
 - Bearer Token Authentication
 - Identity and Access Management (IAM) Authentication
+- VPC Instance Authentication
 - Container Authentication
 - Cloud Pak for Data Authentication
 - No Authentication
@@ -13,13 +14,10 @@ so it is important for the SDK user to consult with the appropriate service docu
 to understand which authenticators are supported for that service.
 
 The python-sdk-core allows an authenticator to be specified in one of two ways:
-1. programmatically - the SDK user invokes the appropriate function(s) to create an instance of the 
-desired authenticator and then passes the authenticator instance when constructing an instance of the service client.
+1. programmatically - the SDK user invokes the appropriate function(s) to create an instance of the
+desired authenticator and then passes the authenticator instance when constructing an instance of the service.
 2. configuration - the SDK user provides external configuration information (in the form of environment variables
-or a credentials file) to indicate the type of authenticator, along with the configuration of the necessary properties
-for that authenticator.
-The SDK user then invokes the configuration-based service client constructor method
-to construct an instance of the authenticator and service client that reflect the external configuration information.
+or a credentials file) to indicate the type of authenticator, along with the configuration of the necessary properties for that authenticator. The SDK user then invokes the configuration-based service client constructor method to construct an instance of the authenticator and service client that reflect the external configuration information.
 
 The sections below will provide detailed information for each authenticator
 which will include the following:
@@ -145,6 +143,15 @@ form:
 
 - url: (optional) The URL representing the IAM token service endpoint.  If not specified, a suitable
 default value is used.
+Make sure that you use an IAM token service endpoint that is appropriate for the
+location of the service being used by your application.
+For example, if you are using an instance of a service in the "production" environment
+(e.g. `https://resource-controller.cloud.ibm.com`),
+then the default "prod" IAM token service endpoint should suffice.
+However, if your application is using an instance of a service in the "staging" environment
+(e.g. `https://resource-controller.test.cloud.ibm.com`),
+then you would also need to configure the authenticator to use the IAM token service "staging"
+endpoint as well (`https://iam.test.cloud.ibm.com`).
 
 - client_id/client_secret: (optional) The `client_id` and `client_secret` fields are used to form a
 "basic auth" Authorization header for interactions with the IAM token server. If neither field
@@ -229,6 +236,15 @@ One of `iam_profile_name` or `iam_profile_id` must be specified.
 - url: (optional) The base endpoint URL of the IAM token service.
 The default value of this property is the "prod" IAM token service endpoint
 (`https://iam.cloud.ibm.com`).
+Make sure that you use an IAM token service endpoint that is appropriate for the
+location of the service being used by your application.
+For example, if you are using an instance of a service in the "production" environment
+(e.g. `https://resource-controller.cloud.ibm.com`),
+then the default "prod" IAM token service endpoint should suffice.
+However, if your application is using an instance of a service in the "staging" environment
+(e.g. `https://resource-controller.test.cloud.ibm.com`),
+then you would also need to configure the authenticator to use the IAM token service "staging"
+endpoint as well (`https://iam.test.cloud.ibm.com`).
 
 - client_id/client_secret: (optional) The `client_id` and `client_secret` fields are used to form a
 "basic auth" Authorization header for interactions with the IAM token server. If neither field
@@ -277,6 +293,76 @@ service = ExampleServiceV1.new_instance(service_name='example_service')
 ```
 
 
+## VPC Instance Authentication
+The `VpcInstanceAuthenticator` is intended to be used by application code
+running inside a VPC-managed compute resource (virtual server instance) that has been configured
+to use the "compute resource identity" feature.
+The compute resource identity feature allows you to assign a trusted IAM profile to the compute resource as its "identity".
+This, in turn, allows applications running within the compute resource to take on this identity when interacting with
+IAM-secured IBM Cloud services.
+This results in a simplified security model that allows the application developer to:
+- avoid storing credentials in application code, configuraton files or a password vault
+- avoid managing or rotating credentials
+
+The `VpcInstanceAuthenticator` will invoke the appropriate operations on the compute resource's locally-available
+VPC Instance Metadata Service to (1) retrieve an instance identity token
+and then (2) exchange that instance identity token for an IAM access token.
+The authenticator will repeat these steps to obtain a new IAM access token whenever the current access token expires.
+The IAM access token is added to each outbound request in the `Authorization` header in the form:
+```
+   Authorization: Bearer <IAM-access-token>
+```
+
+### Properties
+
+- iam_profile_crn: (optional) the crn of the linked trusted IAM profile to be used when obtaining the IAM access token.
+
+- iam_profile_id: (optional) the id of the linked trusted IAM profile to be used when obtaining the IAM access token.
+
+- url: (optional) The VPC Instance Metadata Service's base URL.  
+The default value of this property is `http://169.254.169.254`, and should not need to be specified in normal situations.
+
+Usage Notes:
+1. At most one of `iam_profile_crn` or `iam_profile_id` may be specified.  The specified value must map
+to a trusted IAM profile that has been linked to the compute resource (virtual server instance).
+
+2. If both `iam_profile_crn` and `iam_profile_id` are specified, then an error occurs.
+
+3. If neither `iam_profile_crn` nor `iam_profile_id` are specified, then the default trusted profile linked to the compute resource will be used to perform the IAM token exchange.
+If no default trusted profile is defined for the compute resource, then an error occurs.
+
+
+### Programming example
+```python
+from ibm_cloud_sdk_core.authenticators import VPCInstanceAuthenticator
+from <sdk-package-name>.example_service_v1 import *
+
+# Create the authenticator.
+authenticator = VPCInstanceAuthenticator(iam_profile_crn='crn:iam-profile-123')
+
+# Construct the service instance.
+service = ExampleServiceV1(authenticator=authenticator)
+
+# 'service' can now be used to invoke operations.
+```
+
+### Configuration example
+External configuration:
+```
+export EXAMPLE_SERVICE_AUTH_TYPE=vpc
+export EXAMPLE_SERVICE_IAM_PROFILE_CRN=crn:iam-profile-123
+```
+Application code:
+```python
+from <sdk-package-name>.example_service_v1 import *
+
+# Construct the service instance.
+service = ExampleServiceV1.new_instance(service_name='example_service')
+
+# 'service' can now be used to invoke operations.
+```
+
+
 ##  Cloud Pak for Data
 The `CloudPakForDataAuthenticator` will accept a user-supplied username value, along with either a
 password or apikey, and will 
diff --git a/ibm_cloud_sdk_core/__init__.py b/ibm_cloud_sdk_core/__init__.py
index 8fd7ac40..ff1b8378 100644
--- a/ibm_cloud_sdk_core/__init__.py
+++ b/ibm_cloud_sdk_core/__init__.py
@@ -42,6 +42,7 @@
 from .token_managers.jwt_token_manager import JWTTokenManager
 from .token_managers.cp4d_token_manager import CP4DTokenManager
 from .token_managers.container_token_manager import ContainerTokenManager
+from .token_managers.vpc_instance_token_manager import VPCInstanceTokenManager
 from .api_exception import ApiException
 from .utils import datetime_to_string, string_to_datetime, read_external_sources
 from .utils import datetime_to_string_list, string_to_datetime_list
diff --git a/ibm_cloud_sdk_core/authenticators/__init__.py b/ibm_cloud_sdk_core/authenticators/__init__.py
index cd71d9f2..db7955f8 100644
--- a/ibm_cloud_sdk_core/authenticators/__init__.py
+++ b/ibm_cloud_sdk_core/authenticators/__init__.py
@@ -39,4 +39,5 @@
 from .container_authenticator import ContainerAuthenticator
 from .cp4d_authenticator import CloudPakForDataAuthenticator
 from .iam_authenticator import IAMAuthenticator
+from .vpc_instance_authenticator import VPCInstanceAuthenticator
 from .no_auth_authenticator import NoAuthAuthenticator
diff --git a/ibm_cloud_sdk_core/authenticators/authenticator.py b/ibm_cloud_sdk_core/authenticators/authenticator.py
index 4ceb5a02..112ef35e 100644
--- a/ibm_cloud_sdk_core/authenticators/authenticator.py
+++ b/ibm_cloud_sdk_core/authenticators/authenticator.py
@@ -26,6 +26,7 @@ class Authenticator(ABC):
     AUTHTYPE_IAM = 'iam'
     AUTHTYPE_CONTAINER = 'container'
     AUTHTYPE_CP4D = 'cp4d'
+    AUTHTYPE_VPC = 'vpc'
     AUTHTYPE_NOAUTH = 'noAuth'
     AUTHTYPE_UNKNOWN = 'unknown'
 
diff --git a/ibm_cloud_sdk_core/authenticators/vpc_instance_authenticator.py b/ibm_cloud_sdk_core/authenticators/vpc_instance_authenticator.py
new file mode 100644
index 00000000..c1e4af1d
--- /dev/null
+++ b/ibm_cloud_sdk_core/authenticators/vpc_instance_authenticator.py
@@ -0,0 +1,120 @@
+# coding: utf-8
+
+# Copyright 2021 IBM 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.
+
+from typing import Optional
+
+from requests import Request
+from requests.api import head
+
+from ..token_managers.vpc_instance_token_manager import VPCInstanceTokenManager
+from .authenticator import Authenticator
+
+
+class VPCInstanceAuthenticator(Authenticator):
+    """VPCInstanceAuthenticator implements an authentication scheme in which it
+       retrieves an "instance identity token" and exchanges that
+       for an IAM access token using the VPC Instance Metadata Service API which is available
+       on the local compute resource (VM).
+       The instance identity token is similar to an IAM apikey, except that it is managed
+       automatically by the compute resource provider (VPC).
+       The resulting IAM access token is then added to outbound requests in an Authorization header of the form:
+
+           Authorization: Bearer <access-token>
+
+    Keyword Arguments:
+        iam_profile_crn (str, optional):
+            The CRN of the linked trusted IAM profile to be used as the identity of the compute resource.
+            At most one of iamProfileCrn or iamProfileId may be specified. If neither one is specified,
+            then the default IAM profile defined for the compute resource will be used. Defaults to None.
+        iam_profile_id (str, optional):
+            The ID of the linked trusted IAM profile to be used when obtaining the IAM access token.
+            At most one of iamProfileCrn or iamProfileId may be specified. If neither one is specified,
+            then the default IAM profile defined for the compute resource will be used. Defaults to None.
+        url (str, optional):
+            The VPC Instance Metadata Service's base endpoint URL. Defaults to 'http://169.254.169.254'.
+
+    Attributes:
+        iam_profile_crn (str, optional): The CRN of the linked trusted IAM profile.
+        iam_profile_id (str, optional): The ID of the linked trusted IAM profile.
+        url (str, optional): The VPC Instance Metadata Service's base endpoint URL.
+    """
+
+    def __init__(self,
+                 iam_profile_crn: Optional[str] = None,
+                 iam_profile_id: Optional[str] = None,
+                 url: Optional[str] = 'http://169.254.169.254') -> None:
+
+        self.token_manager = VPCInstanceTokenManager(
+            url=url, iam_profile_crn=iam_profile_crn, iam_profile_id=iam_profile_id)
+
+        self.validate()
+
+    def authentication_type(self) -> str:
+        """Returns this authenticator's type ('VPC')."""
+        return Authenticator.AUTHTYPE_VPC
+
+    def validate(self) -> None:
+        super().validate()
+
+        if self.token_manager.iam_profile_crn and self.token_manager.iam_profile_id:
+            raise ValueError(
+                'At most one of "iam_profile_id" or "iam_profile_crn" may be specified.')
+
+    def authenticate(self, req: Request) -> None:
+        """Adds IAM authentication information to the request.
+
+        The IAM access token will be added to the request's headers in the form:
+
+            Authorization: Bearer <bearer-token>
+
+        Args:
+            req: The request to add IAM authentication information too. Must contain a key to a dictionary
+            called headers.
+        """
+        headers = req.get('headers')
+        bearer_token = self.token_manager.get_token()
+        headers['Authorization'] = 'Bearer {0}'.format(bearer_token)
+
+
+    def set_iam_profile_crn(self, iam_profile_crn: str) -> None:
+        """Sets CRN of the IAM profile.
+
+        Args:
+            iam_profile_crn (str): the CRN of the linked trusted IAM profile to be used as
+                             the identity of the compute resource.
+
+        Raises:
+            ValueError: At most one of iam_profile_crn or iam_profile_id may be specified.
+                        If neither one is specified, then the default IAM profile defined
+                        for the compute resource will be used.
+        """
+        self.token_manager.set_iam_profile_crn(iam_profile_crn)
+        self.validate()
+
+    def set_iam_profile_id(self, iam_profile_id: str) -> None:
+        """Sets the ID of the IAM profile.
+
+        Args:
+            iam_profile_id (str): id of the linked trusted IAM profile to be used when obtaining
+                            the IAM access token
+
+        Raises:
+            ValueError: At most one of iam_profile_crn or iam_profile_id may be specified.
+                        If neither one is specified, then the default IAM profile defined
+                        for the compute resource will be used.
+        """
+        self.token_manager.set_iam_profile_id(iam_profile_id)
+        self.validate()
diff --git a/ibm_cloud_sdk_core/get_authenticator.py b/ibm_cloud_sdk_core/get_authenticator.py
index e9e62973..f9742afe 100644
--- a/ibm_cloud_sdk_core/get_authenticator.py
+++ b/ibm_cloud_sdk_core/get_authenticator.py
@@ -14,6 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from ibm_cloud_sdk_core.authenticators.vpc_instance_authenticator import VPCInstanceAuthenticator
 from .authenticators import (Authenticator, BasicAuthenticator, BearerTokenAuthenticator, ContainerAuthenticator,
                              CloudPakForDataAuthenticator, IAMAuthenticator, NoAuthAuthenticator)
 from .utils import read_external_sources
@@ -91,6 +92,11 @@ def __construct_authenticator(config: dict) -> Authenticator:
             disable_ssl_verification=config.get(
                 'AUTH_DISABLE_SSL', 'false').lower() == 'true',
             scope=config.get('SCOPE'))
+    elif auth_type == Authenticator.AUTHTYPE_VPC.lower():
+        authenticator = VPCInstanceAuthenticator(
+            iam_profile_crn=config.get('IAM_PROFILE_CRN'),
+            iam_profile_id=config.get('IAM_PROFILE_ID'),
+            url=config.get('AUTH_URL'))
     elif auth_type == Authenticator.AUTHTYPE_NOAUTH.lower():
         authenticator = NoAuthAuthenticator()
 
diff --git a/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py b/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py
new file mode 100644
index 00000000..8d46a6a0
--- /dev/null
+++ b/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py
@@ -0,0 +1,150 @@
+# coding: utf-8
+
+# Copyright 2021 IBM 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 logging
+from typing import Optional
+
+from .jwt_token_manager import JWTTokenManager
+
+
+class VPCInstanceTokenManager(JWTTokenManager):
+    """The VPCInstanceTokenManager retrieves an "instance identity token" and exchanges that
+       for an IAM access token using the VPC Instance Metadata Service API which is available
+       on the local compute resource (VM).
+       The instance identity token is similar to an IAM apikey, except that it is managed
+       automatically by the compute resource provider (VPC).
+       The resulting IAM access token is then added to outbound requests in an Authorization header of the form:
+
+           Authorization: Bearer <access-token>
+
+    Keyword Arguments:
+        iam_profile_crn (str, optional):
+            The CRN of the linked trusted IAM profile to be used as the identity of the compute resource.
+            At most one of iamProfileCrn or iamProfileId may be specified. If neither one is specified,
+            then the default IAM profile defined for the compute resource will be used. Defaults to None.
+        iam_profile_id (str, optional):
+            The ID of the linked trusted IAM profile to be used when obtaining the IAM access token.
+            At most one of iamProfileCrn or iamProfileId may be specified. If neither one is specified,
+            then the default IAM profile defined for the compute resource will be used. Defaults to None.
+        url (str, optional):
+            The VPC Instance Metadata Service's base endpoint URL. Defaults to 'http://169.254.169.254'.
+
+    Attributes:
+        iam_profile_crn (str, optional): The CRN of the linked trusted IAM profile.
+        iam_profile_id (str, optional): The ID of the linked trusted IAM profile.
+        url (str, optional): The VPC Instance Metadata Service's base endpoint URL.
+    """
+
+    METADATA_SERVICE_VERSION = '2021-09-20'
+
+    def __init__(self,
+                 iam_profile_crn: Optional[str] = None,
+                 iam_profile_id: Optional[str] = None,
+                 url: Optional[str] = 'http://169.254.169.254') -> None:
+        super().__init__(url)
+
+        self.iam_profile_crn = iam_profile_crn
+        self.iam_profile_id = iam_profile_id
+
+    def request_token(self) -> dict:
+        """RequestToken will use the VPC Instance Metadata Service to
+           (1) retrieve a fresh instance identity token and then
+           (2) exchange that for an IAM access token.
+
+        Returns:
+            A dictionary containing the bearer token to be subsequently used service requests.
+        """
+
+        # Retrieve the Instance Identity Token first.
+        instance_identity_token = self.retrieve_instance_identity_token()
+
+        url = self.url + '/instance_identity/v1/iam_token'
+
+        if self.iam_profile_crn:
+            #pylint: disable=missing-format-argument-key
+            request_payload = {
+                'trusted_profile': '{"crn": "{0}"}'.format(self.iam_profile_crn)}
+        if self.iam_profile_id:
+            #pylint: disable=missing-format-argument-key
+            request_payload = {
+                'trusted_profile': '{"id": "{0}"}'.format(self.iam_profile_id)}
+
+        headers = {
+            'Content-Type': 'application/json',
+            'Accept': 'application/json',
+            'Authorization': 'Bearer ' + instance_identity_token
+        }
+
+        logging.debug(
+            'Invoking VPC \'create_iam_token\' operation: %s', url)
+        response = self._request(
+            method='POST',
+            url=(self.url + self.OPERATION_PATH) if self.url else self.url,
+            headers=headers,
+            params={'version': self.METADATA_SERVICE_VERSION},
+            data=request_payload)
+        logging.debug('Returned from VPC \'create_access_token\' operation."')
+
+        return response
+
+    def set_iam_profile_crn(self, iam_profile_crn: str) -> None:
+        """Sets the CRN of the IAM profile.
+
+        Args:
+            iam_profile_crn (str): the CRN of the linked trusted IAM profile to be used as
+                             the identity of the compute resource.
+        """
+        self.iam_profile_crn = iam_profile_crn
+
+    def set_iam_profile_id(self, iam_profile_id: str) -> None:
+        """Sets the ID of the IAM profile.
+
+        Args:
+            iam_profile_id (str): id of the linked trusted IAM profile to be used when obtaining
+                            the IAM access token
+        """
+        self.iam_profile_id = iam_profile_id
+
+    def retrieve_instance_identity_token(self) -> str:
+        """Retrieves the local compute resource's instance identity token using
+           the "create_access_token" operation of the local VPC Instance Metadata Service API.
+
+        Returns:
+            The retrieved instance identity token string.
+        """
+
+        url = self.url + '/instance_identity/v1/token'
+
+        headers = {
+            'version': self.METADATA_SERVICE_VERSION,
+            'Content-type': 'application/json',
+            'Accept': 'application/json',
+            'Metadata-Flavor': 'ibm',
+        }
+
+        request_body = {'expires_in': 300}
+
+        logging.debug(
+            'Invoking VPC \'create_access_token\' operation: %s', url)
+        response = self._request(
+            method='PUT',
+            url=url,
+            headers=headers,
+            data=request_body,
+            proxies=self.proxies)
+        logging.debug('Returned from VPC \'create_access_token\' operation."')
+
+        return response['access_token']

From f90f4d82d4e495109e74f6abe9041264006e8507 Mon Sep 17 00:00:00 2001
From: Norbert Biczo <pyrooka@users.noreply.github.com>
Date: Tue, 26 Oct 2021 18:35:18 +0200
Subject: [PATCH 2/8] chore: fix some issues

---
 .../vpc_instance_authenticator.py             |  8 +++++--
 .../vpc_instance_token_manager.py             | 22 ++++++++++---------
 2 files changed, 18 insertions(+), 12 deletions(-)

diff --git a/ibm_cloud_sdk_core/authenticators/vpc_instance_authenticator.py b/ibm_cloud_sdk_core/authenticators/vpc_instance_authenticator.py
index c1e4af1d..4893f873 100644
--- a/ibm_cloud_sdk_core/authenticators/vpc_instance_authenticator.py
+++ b/ibm_cloud_sdk_core/authenticators/vpc_instance_authenticator.py
@@ -17,7 +17,6 @@
 from typing import Optional
 
 from requests import Request
-from requests.api import head
 
 from ..token_managers.vpc_instance_token_manager import VPCInstanceTokenManager
 from .authenticator import Authenticator
@@ -52,10 +51,15 @@ class VPCInstanceAuthenticator(Authenticator):
         url (str, optional): The VPC Instance Metadata Service's base endpoint URL.
     """
 
+    DEFAULT_IMS_ENDPOINT = 'http://169.254.169.254'
+
     def __init__(self,
                  iam_profile_crn: Optional[str] = None,
                  iam_profile_id: Optional[str] = None,
-                 url: Optional[str] = 'http://169.254.169.254') -> None:
+                 url: Optional[str] = None) -> None:
+
+        if not url:
+            url = self.DEFAULT_IMS_ENDPOINT
 
         self.token_manager = VPCInstanceTokenManager(
             url=url, iam_profile_crn=iam_profile_crn, iam_profile_id=iam_profile_id)
diff --git a/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py b/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py
index 8d46a6a0..0b7df1dd 100644
--- a/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py
+++ b/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py
@@ -14,6 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import json
 import logging
 from typing import Optional
 
@@ -49,11 +50,15 @@ class VPCInstanceTokenManager(JWTTokenManager):
     """
 
     METADATA_SERVICE_VERSION = '2021-09-20'
+    DEFAULT_IMS_ENDPOINT = 'http://169.254.169.254'
 
     def __init__(self,
                  iam_profile_crn: Optional[str] = None,
                  iam_profile_id: Optional[str] = None,
-                 url: Optional[str] = 'http://169.254.169.254') -> None:
+                 url: Optional[str] = None) -> None:
+        if not url:
+            url = self.DEFAULT_IMS_ENDPOINT
+
         super().__init__(url)
 
         self.iam_profile_crn = iam_profile_crn
@@ -74,13 +79,11 @@ def request_token(self) -> dict:
         url = self.url + '/instance_identity/v1/iam_token'
 
         if self.iam_profile_crn:
-            #pylint: disable=missing-format-argument-key
             request_payload = {
-                'trusted_profile': '{"crn": "{0}"}'.format(self.iam_profile_crn)}
+                'trusted_profile': '{{"crn": "{0}"}}'.format(self.iam_profile_crn)}
         if self.iam_profile_id:
-            #pylint: disable=missing-format-argument-key
             request_payload = {
-                'trusted_profile': '{"id": "{0}"}'.format(self.iam_profile_id)}
+                'trusted_profile': '{{"id": "{0}"}}'.format(self.iam_profile_id)}
 
         headers = {
             'Content-Type': 'application/json',
@@ -92,11 +95,11 @@ def request_token(self) -> dict:
             'Invoking VPC \'create_iam_token\' operation: %s', url)
         response = self._request(
             method='POST',
-            url=(self.url + self.OPERATION_PATH) if self.url else self.url,
+            url=url,
             headers=headers,
             params={'version': self.METADATA_SERVICE_VERSION},
-            data=request_payload)
-        logging.debug('Returned from VPC \'create_access_token\' operation."')
+            data=json.dumps(request_payload))
+        logging.debug('Returned from VPC \'create_iam_token\' operation."')
 
         return response
 
@@ -143,8 +146,7 @@ def retrieve_instance_identity_token(self) -> str:
             method='PUT',
             url=url,
             headers=headers,
-            data=request_body,
-            proxies=self.proxies)
+            data=json.dumps(request_body))
         logging.debug('Returned from VPC \'create_access_token\' operation."')
 
         return response['access_token']

From e6007168063bdf7031774805f62b4e436374eb29 Mon Sep 17 00:00:00 2001
From: Norbert Biczo <pyrooka@users.noreply.github.com>
Date: Tue, 26 Oct 2021 19:31:41 +0200
Subject: [PATCH 3/8] test: add unit tests for VPC stuffs

---
 resources/ibm-credentials-vpc.env       |  11 ++
 test/test_utils.py                      |  30 ++++
 test/test_vpc_instance_authenticator.py |  66 ++++++++
 test/test_vpc_instance_token_manager.py | 203 ++++++++++++++++++++++++
 4 files changed, 310 insertions(+)
 create mode 100644 resources/ibm-credentials-vpc.env
 create mode 100644 test/test_vpc_instance_authenticator.py
 create mode 100644 test/test_vpc_instance_token_manager.py

diff --git a/resources/ibm-credentials-vpc.env b/resources/ibm-credentials-vpc.env
new file mode 100644
index 00000000..3ad2f8f9
--- /dev/null
+++ b/resources/ibm-credentials-vpc.env
@@ -0,0 +1,11 @@
+# VPC auth with default config
+SERVICE1_AUTH_TYPE=vpc
+
+# VPC auth with profile CRN
+SERVICE2_AUTH_TYPE=vpc
+SERVICE2_IAM_PROFILE_CRN=crn:iam-profile1
+SERVICE2_AUTH_URL=http://vpc.imds.com/api
+
+# VPC auth with profile ID
+SERVICE3_AUTH_TYPE=vpc
+SERVICE3_IAM_PROFILE_ID=iam-profile1-id
\ No newline at end of file
diff --git a/test/test_utils.py b/test/test_utils.py
index 35c00dcd..83477446 100644
--- a/test/test_utils.py
+++ b/test/test_utils.py
@@ -376,6 +376,36 @@ def test_get_authenticator_from_credential_file():
     assert authenticator.token_manager.scope is None
     del os.environ['IBM_CREDENTIALS_FILE']
 
+    file_path = os.path.join(os.path.dirname(__file__),
+                             '../resources/ibm-credentials-vpc.env')
+    os.environ['IBM_CREDENTIALS_FILE'] = file_path
+    authenticator = get_authenticator_from_environment('service1')
+    assert authenticator is not None
+    assert authenticator.authentication_type() == Authenticator.AUTHTYPE_VPC
+    assert authenticator.token_manager.iam_profile_crn is None
+    assert authenticator.token_manager.iam_profile_id is None
+    assert authenticator.token_manager.url == 'http://169.254.169.254'
+
+    file_path = os.path.join(os.path.dirname(__file__),
+                             '../resources/ibm-credentials-vpc.env')
+    os.environ['IBM_CREDENTIALS_FILE'] = file_path
+    authenticator = get_authenticator_from_environment('service2')
+    assert authenticator is not None
+    assert authenticator.authentication_type() == Authenticator.AUTHTYPE_VPC
+    assert authenticator.token_manager.iam_profile_crn == 'crn:iam-profile1'
+    assert authenticator.token_manager.iam_profile_id is None
+    assert authenticator.token_manager.url == 'http://vpc.imds.com/api'
+
+    file_path = os.path.join(os.path.dirname(__file__),
+                             '../resources/ibm-credentials-vpc.env')
+    os.environ['IBM_CREDENTIALS_FILE'] = file_path
+    authenticator = get_authenticator_from_environment('service3')
+    assert authenticator is not None
+    assert authenticator.authentication_type() == Authenticator.AUTHTYPE_VPC
+    assert authenticator.token_manager.iam_profile_crn is None
+    assert authenticator.token_manager.iam_profile_id == 'iam-profile1-id'
+    assert authenticator.token_manager.url == 'http://169.254.169.254'
+
 
 def test_get_authenticator_from_credential_file_scope():
     file_path = os.path.join(os.path.dirname(__file__),
diff --git a/test/test_vpc_instance_authenticator.py b/test/test_vpc_instance_authenticator.py
new file mode 100644
index 00000000..36a4c3b6
--- /dev/null
+++ b/test/test_vpc_instance_authenticator.py
@@ -0,0 +1,66 @@
+# pylint: disable=missing-docstring
+import pytest
+
+from ibm_cloud_sdk_core.authenticators import VPCInstanceAuthenticator, Authenticator
+
+
+TEST_IAM_PROFILE_CRN = 'crn:iam-profile:123'
+TEST_IAM_PROFILE_ID = 'iam-id-123'
+
+
+def test_constructor():
+    authenticator =VPCInstanceAuthenticator(iam_profile_id=TEST_IAM_PROFILE_ID, url='someurl.com')
+    assert authenticator is not None
+    assert authenticator.authentication_type() == Authenticator.AUTHTYPE_VPC
+    assert authenticator.token_manager.iam_profile_crn is None
+    assert authenticator.token_manager.iam_profile_id == TEST_IAM_PROFILE_ID
+    assert authenticator.token_manager.url == 'someurl.com'
+
+
+def test_setters():
+    authenticator =VPCInstanceAuthenticator(iam_profile_id=TEST_IAM_PROFILE_ID, url='someurl.com')
+    assert authenticator is not None
+    assert authenticator.authentication_type() == Authenticator.AUTHTYPE_VPC
+    assert authenticator.token_manager.iam_profile_crn is None
+    assert authenticator.token_manager.iam_profile_id == TEST_IAM_PROFILE_ID
+    assert authenticator.token_manager.url == 'someurl.com'
+
+    # Set the IAM profile CRN to trigger a validation which will fail,
+    # because at most one of iam_profile_crn or iam_profile_id may be specified.
+    with pytest.raises(ValueError) as err:
+        authenticator.set_iam_profile_crn(TEST_IAM_PROFILE_CRN)
+    assert str(
+        err.value) == 'At most one of "iam_profile_id" or "iam_profile_crn" may be specified.'
+
+    authenticator.set_iam_profile_id(None)
+    assert authenticator.token_manager.iam_profile_id is None
+
+    authenticator.set_iam_profile_crn(TEST_IAM_PROFILE_CRN)
+    assert authenticator.token_manager.iam_profile_crn == TEST_IAM_PROFILE_CRN
+
+
+def test_constructor_validate_failed():
+    with pytest.raises(ValueError) as err:
+        VPCInstanceAuthenticator(
+		iam_profile_crn=TEST_IAM_PROFILE_CRN,
+		iam_profile_id=TEST_IAM_PROFILE_ID,
+	)
+    assert str(
+        err.value) == 'At most one of "iam_profile_id" or "iam_profile_crn" may be specified.'
+
+
+def test_authenticate():
+    def mock_get_token():
+        return 'mock_token'
+
+    authenticator = VPCInstanceAuthenticator(iam_profile_crn=TEST_IAM_PROFILE_CRN)
+    authenticator.token_manager.get_token = mock_get_token
+
+    # Simulate an SDK API request that needs to be authenticated.
+    request = {'headers': {}}
+
+    # Trigger the "get token" processing to obtain the access token and add to the "SDK request".
+    authenticator.authenticate(request)
+
+    # Verify that the "authenticate()" method added the Authorization header
+    assert request['headers']['Authorization'] == 'Bearer mock_token'
diff --git a/test/test_vpc_instance_token_manager.py b/test/test_vpc_instance_token_manager.py
new file mode 100644
index 00000000..c091d854
--- /dev/null
+++ b/test/test_vpc_instance_token_manager.py
@@ -0,0 +1,203 @@
+# coding: utf-8
+
+# Copyright 2021 IBM 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.
+
+# pylint: disable=missing-docstring
+import json
+import logging
+
+import pytest
+import responses
+
+from ibm_cloud_sdk_core import ApiException, VPCInstanceTokenManager
+
+
+TEST_TOKEN = 'abc123'
+TEST_IAM_TOKEN = 'iam-abc123'
+TEST_IAM_PROFILE_CRN = 'crn:iam-profile:123'
+TEST_IAM_PROFILE_ID = 'iam-id-123'
+
+
+def test_constructor():
+    token_manager = VPCInstanceTokenManager(
+        iam_profile_crn=TEST_IAM_PROFILE_CRN,
+    )
+
+    assert token_manager.iam_profile_crn is TEST_IAM_PROFILE_CRN
+    assert token_manager.iam_profile_id is None
+    assert token_manager.access_token is None
+
+
+def test_setters():
+    token_manager = VPCInstanceTokenManager(
+        iam_profile_crn=TEST_IAM_PROFILE_CRN,
+    )
+
+    assert token_manager.iam_profile_crn is TEST_IAM_PROFILE_CRN
+    assert token_manager.iam_profile_id is None
+    assert token_manager.access_token is None
+
+    token_manager.set_iam_profile_crn(None)
+    assert token_manager.iam_profile_crn is None
+
+    token_manager.set_iam_profile_id(TEST_IAM_PROFILE_ID)
+    assert token_manager.iam_profile_id == TEST_IAM_PROFILE_ID
+
+
+@responses.activate
+def test_retrieve_instance_identity_token(caplog):
+    caplog.set_level(logging.DEBUG)
+
+    token_manager = VPCInstanceTokenManager(
+        iam_profile_crn=TEST_IAM_PROFILE_CRN,
+        url='http://someurl.com',
+    )
+
+    response = {
+        'access_token': TEST_TOKEN,
+    }
+
+    responses.add(responses.PUT, 'http://someurl.com/instance_identity/v1/token',
+                  body=json.dumps(response), status=200)
+
+    ii_token = token_manager.retrieve_instance_identity_token()
+    assert len(responses.calls) == 1
+    assert responses.calls[0].request.headers['version'] == '2021-09-20'
+    assert responses.calls[0].request.headers['Content-Type'] == 'application/json'
+    assert responses.calls[0].request.headers['Accept'] == 'application/json'
+    assert responses.calls[0].request.headers['Metadata-Flavor'] == 'ibm'
+    assert responses.calls[0].request.body == '{"expires_in": 300}'
+    assert ii_token == TEST_TOKEN
+    # Check the logs.
+    #pylint: disable=line-too-long
+    assert caplog.record_tuples[0][2] == 'Invoking VPC \'create_access_token\' operation: http://someurl.com/instance_identity/v1/token'
+    assert caplog.record_tuples[1][2] == 'Returned from VPC \'create_access_token\' operation."'
+
+@responses.activate
+def test_retrieve_instance_identity_token_failed(caplog):
+    caplog.set_level(logging.DEBUG)
+
+    token_manager = VPCInstanceTokenManager(
+        iam_profile_crn=TEST_IAM_PROFILE_CRN,
+        url='http://someurl.com',
+    )
+
+    response = {
+        'errors': ['Ooops'],
+    }
+
+    responses.add(responses.PUT, 'http://someurl.com/instance_identity/v1/token',
+                  body=json.dumps(response), status=400)
+
+    with pytest.raises(ApiException):
+        token_manager.retrieve_instance_identity_token()
+
+    assert len(responses.calls) == 1
+    # Check the logs.
+    #pylint: disable=line-too-long
+    assert caplog.record_tuples[0][2] == 'Invoking VPC \'create_access_token\' operation: http://someurl.com/instance_identity/v1/token'
+
+@responses.activate
+def test_request_token_with_crn(caplog):
+    caplog.set_level(logging.DEBUG)
+
+    token_manager = VPCInstanceTokenManager(
+        iam_profile_crn=TEST_IAM_PROFILE_CRN,
+    )
+
+    # Mock the retrieve instance identity token method.
+    def mock_retrieve_instance_identity_token():
+        return TEST_TOKEN
+    token_manager.retrieve_instance_identity_token = mock_retrieve_instance_identity_token
+
+    response = {
+        'access_token': TEST_IAM_TOKEN,
+    }
+
+    responses.add(responses.POST, 'http://169.254.169.254/instance_identity/v1/iam_token',
+                  body=json.dumps(response), status=200)
+
+    response = token_manager.request_token()
+    assert len(responses.calls) == 1
+    assert responses.calls[0].request.headers['Content-Type'] == 'application/json'
+    assert responses.calls[0].request.headers['Accept'] == 'application/json'
+    assert responses.calls[0].request.headers['Authorization'] == 'Bearer ' + TEST_TOKEN
+    assert responses.calls[0].request.body == '{"trusted_profile": "{\\"crn\\": \\"crn:iam-profile:123\\"}"}'
+    assert responses.calls[0].request.params['version'] == '2021-09-20'
+    # Check the logs.
+    #pylint: disable=line-too-long
+    assert caplog.record_tuples[0][2] == 'Invoking VPC \'create_iam_token\' operation: http://169.254.169.254/instance_identity/v1/iam_token'
+    assert caplog.record_tuples[1][2] == 'Returned from VPC \'create_iam_token\' operation."'
+
+
+@responses.activate
+def test_request_token_with_id(caplog):
+    caplog.set_level(logging.DEBUG)
+
+    token_manager = VPCInstanceTokenManager(
+        iam_profile_id=TEST_IAM_PROFILE_ID,
+    )
+
+    # Mock the retrieve instance identity token method.
+    def mock_retrieve_instance_identity_token():
+        return TEST_TOKEN
+    token_manager.retrieve_instance_identity_token = mock_retrieve_instance_identity_token
+
+    response = {
+        'access_token': TEST_IAM_TOKEN,
+    }
+
+    responses.add(responses.POST, 'http://169.254.169.254/instance_identity/v1/iam_token',
+                  body=json.dumps(response), status=200)
+
+    response = token_manager.request_token()
+    assert len(responses.calls) == 1
+    assert responses.calls[0].request.headers['Content-Type'] == 'application/json'
+    assert responses.calls[0].request.headers['Accept'] == 'application/json'
+    assert responses.calls[0].request.headers['Authorization'] == 'Bearer ' + TEST_TOKEN
+    assert responses.calls[0].request.body == '{"trusted_profile": "{\\"id\\": \\"iam-id-123\\"}"}'
+    assert responses.calls[0].request.params['version'] == '2021-09-20'
+    # Check the logs.
+    #pylint: disable=line-too-long
+    assert caplog.record_tuples[0][2] == 'Invoking VPC \'create_iam_token\' operation: http://169.254.169.254/instance_identity/v1/iam_token'
+    assert caplog.record_tuples[1][2] == 'Returned from VPC \'create_iam_token\' operation."'
+
+
+@responses.activate
+def test_request_token_with_failed(caplog):
+    caplog.set_level(logging.DEBUG)
+
+    token_manager = VPCInstanceTokenManager(
+        iam_profile_id=TEST_IAM_PROFILE_ID,
+    )
+
+    # Mock the retrieve instance identity token method.
+    def mock_retrieve_instance_identity_token():
+        return TEST_TOKEN
+    token_manager.retrieve_instance_identity_token = mock_retrieve_instance_identity_token
+
+    response = {
+        'errors': ['Ooops'],
+    }
+
+    responses.add(responses.POST, 'http://169.254.169.254/instance_identity/v1/iam_token',
+                  body=json.dumps(response), status=400)
+
+    with pytest.raises(ApiException):
+        token_manager.request_token()
+    assert len(responses.calls) == 1
+    # Check the logs.
+    #pylint: disable=line-too-long
+    assert caplog.record_tuples[0][2] == 'Invoking VPC \'create_iam_token\' operation: http://169.254.169.254/instance_identity/v1/iam_token'

From 0795efa09573525fa8b32e55a9b59933b3a49e26 Mon Sep 17 00:00:00 2001
From: Norbert Biczo <pyrooka@users.noreply.github.com>
Date: Fri, 29 Oct 2021 09:34:23 +0200
Subject: [PATCH 4/8] chore: set access_token properly

Co-authored-by: Matthew Keezer <Robert.Keezer@ibm.com>
---
 .../vpc_instance_token_manager.py             |  3 +-
 test/test_vpc_instance_token_manager.py       | 34 ++++++++++++++++++-
 2 files changed, 35 insertions(+), 2 deletions(-)

diff --git a/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py b/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py
index 0b7df1dd..1434c0ac 100644
--- a/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py
+++ b/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py
@@ -51,6 +51,7 @@ class VPCInstanceTokenManager(JWTTokenManager):
 
     METADATA_SERVICE_VERSION = '2021-09-20'
     DEFAULT_IMS_ENDPOINT = 'http://169.254.169.254'
+    TOKEN_NAME = 'access_token'
 
     def __init__(self,
                  iam_profile_crn: Optional[str] = None,
@@ -59,7 +60,7 @@ def __init__(self,
         if not url:
             url = self.DEFAULT_IMS_ENDPOINT
 
-        super().__init__(url)
+        super().__init__(url, token_name=self.TOKEN_NAME)
 
         self.iam_profile_crn = iam_profile_crn
         self.iam_profile_id = iam_profile_id
diff --git a/test/test_vpc_instance_token_manager.py b/test/test_vpc_instance_token_manager.py
index c091d854..02428b77 100644
--- a/test/test_vpc_instance_token_manager.py
+++ b/test/test_vpc_instance_token_manager.py
@@ -24,6 +24,8 @@
 from ibm_cloud_sdk_core import ApiException, VPCInstanceTokenManager
 
 
+#pylint: disable=line-too-long
+TEST_ACCESS_TOKEN = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImhlbGxvIiwicm9sZSI6InVzZXIiLCJwZXJtaXNzaW9ucyI6WyJhZG1pbmlzdHJhdG9yIiwiZGVwbG95bWVudF9hZG1pbiJdLCJzdWIiOiJoZWxsbyIsImlzcyI6IkpvaG4iLCJhdWQiOiJEU1giLCJ1aWQiOiI5OTkiLCJpYXQiOjE1NjAyNzcwNTEsImV4cCI6MTU2MDI4MTgxOSwianRpIjoiMDRkMjBiMjUtZWUyZC00MDBmLTg2MjMtOGNkODA3MGI1NDY4In0.cIodB4I6CCcX8vfIImz7Cytux3GpWyObt9Gkur5g1QI'
 TEST_TOKEN = 'abc123'
 TEST_IAM_TOKEN = 'iam-abc123'
 TEST_IAM_PROFILE_CRN = 'crn:iam-profile:123'
@@ -85,6 +87,7 @@ def test_retrieve_instance_identity_token(caplog):
     assert caplog.record_tuples[0][2] == 'Invoking VPC \'create_access_token\' operation: http://someurl.com/instance_identity/v1/token'
     assert caplog.record_tuples[1][2] == 'Returned from VPC \'create_access_token\' operation."'
 
+
 @responses.activate
 def test_retrieve_instance_identity_token_failed(caplog):
     caplog.set_level(logging.DEBUG)
@@ -109,6 +112,7 @@ def test_retrieve_instance_identity_token_failed(caplog):
     #pylint: disable=line-too-long
     assert caplog.record_tuples[0][2] == 'Invoking VPC \'create_access_token\' operation: http://someurl.com/instance_identity/v1/token'
 
+
 @responses.activate
 def test_request_token_with_crn(caplog):
     caplog.set_level(logging.DEBUG)
@@ -176,7 +180,7 @@ def mock_retrieve_instance_identity_token():
 
 
 @responses.activate
-def test_request_token_with_failed(caplog):
+def test_request_token_failed(caplog):
     caplog.set_level(logging.DEBUG)
 
     token_manager = VPCInstanceTokenManager(
@@ -201,3 +205,31 @@ def mock_retrieve_instance_identity_token():
     # Check the logs.
     #pylint: disable=line-too-long
     assert caplog.record_tuples[0][2] == 'Invoking VPC \'create_iam_token\' operation: http://169.254.169.254/instance_identity/v1/iam_token'
+
+
+@responses.activate
+def test_access_token():
+    token_manager = VPCInstanceTokenManager(
+        iam_profile_id=TEST_IAM_PROFILE_ID,
+    )
+
+    response_ii = {
+        'access_token': TEST_TOKEN,
+    }
+    response_iam = {
+        'access_token': TEST_ACCESS_TOKEN,
+    }
+
+    responses.add(responses.PUT, 'http://169.254.169.254/instance_identity/v1/token',
+                  body=json.dumps(response_ii), status=200)
+    responses.add(responses.POST, 'http://169.254.169.254/instance_identity/v1/iam_token',
+                  body=json.dumps(response_iam), status=200)
+
+    assert token_manager.access_token is None
+    assert token_manager.expire_time == 0
+    assert token_manager.refresh_time == 0
+
+    token_manager.get_token()
+    assert token_manager.access_token == TEST_ACCESS_TOKEN
+    assert token_manager.expire_time > 0
+    assert token_manager.refresh_time > 0

From e30e371c9ebb78d2ce81175ce13a82a86b8bdddb Mon Sep 17 00:00:00 2001
From: Norbert Biczo <pyrooka@users.noreply.github.com>
Date: Fri, 29 Oct 2021 09:58:00 +0200
Subject: [PATCH 5/8] chore: address issues 1

Co-authored-by: Matthew Keezer <Robert.Keezer@ibm.com>
---
 .../token_managers/vpc_instance_token_manager.py          | 8 +++-----
 test/test_vpc_instance_token_manager.py                   | 6 +++---
 2 files changed, 6 insertions(+), 8 deletions(-)

diff --git a/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py b/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py
index 1434c0ac..173be489 100644
--- a/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py
+++ b/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py
@@ -80,11 +80,9 @@ def request_token(self) -> dict:
         url = self.url + '/instance_identity/v1/iam_token'
 
         if self.iam_profile_crn:
-            request_payload = {
-                'trusted_profile': '{{"crn": "{0}"}}'.format(self.iam_profile_crn)}
+            request_payload = {'trusted_profile': {'crn': self.iam_profile_crn}}
         if self.iam_profile_id:
-            request_payload = {
-                'trusted_profile': '{{"id": "{0}"}}'.format(self.iam_profile_id)}
+            request_payload = {'trusted_profile': {'id': self.iam_profile_id}}
 
         headers = {
             'Content-Type': 'application/json',
@@ -133,7 +131,6 @@ def retrieve_instance_identity_token(self) -> str:
         url = self.url + '/instance_identity/v1/token'
 
         headers = {
-            'version': self.METADATA_SERVICE_VERSION,
             'Content-type': 'application/json',
             'Accept': 'application/json',
             'Metadata-Flavor': 'ibm',
@@ -147,6 +144,7 @@ def retrieve_instance_identity_token(self) -> str:
             method='PUT',
             url=url,
             headers=headers,
+            params={'version': self.METADATA_SERVICE_VERSION},
             data=json.dumps(request_body))
         logging.debug('Returned from VPC \'create_access_token\' operation."')
 
diff --git a/test/test_vpc_instance_token_manager.py b/test/test_vpc_instance_token_manager.py
index 02428b77..76bceb21 100644
--- a/test/test_vpc_instance_token_manager.py
+++ b/test/test_vpc_instance_token_manager.py
@@ -76,10 +76,10 @@ def test_retrieve_instance_identity_token(caplog):
 
     ii_token = token_manager.retrieve_instance_identity_token()
     assert len(responses.calls) == 1
-    assert responses.calls[0].request.headers['version'] == '2021-09-20'
     assert responses.calls[0].request.headers['Content-Type'] == 'application/json'
     assert responses.calls[0].request.headers['Accept'] == 'application/json'
     assert responses.calls[0].request.headers['Metadata-Flavor'] == 'ibm'
+    assert responses.calls[0].request.params['version'] == '2021-09-20'
     assert responses.calls[0].request.body == '{"expires_in": 300}'
     assert ii_token == TEST_TOKEN
     # Check the logs.
@@ -138,7 +138,7 @@ def mock_retrieve_instance_identity_token():
     assert responses.calls[0].request.headers['Content-Type'] == 'application/json'
     assert responses.calls[0].request.headers['Accept'] == 'application/json'
     assert responses.calls[0].request.headers['Authorization'] == 'Bearer ' + TEST_TOKEN
-    assert responses.calls[0].request.body == '{"trusted_profile": "{\\"crn\\": \\"crn:iam-profile:123\\"}"}'
+    assert responses.calls[0].request.body == '{"trusted_profile": {"crn": "crn:iam-profile:123"}}'
     assert responses.calls[0].request.params['version'] == '2021-09-20'
     # Check the logs.
     #pylint: disable=line-too-long
@@ -171,7 +171,7 @@ def mock_retrieve_instance_identity_token():
     assert responses.calls[0].request.headers['Content-Type'] == 'application/json'
     assert responses.calls[0].request.headers['Accept'] == 'application/json'
     assert responses.calls[0].request.headers['Authorization'] == 'Bearer ' + TEST_TOKEN
-    assert responses.calls[0].request.body == '{"trusted_profile": "{\\"id\\": \\"iam-id-123\\"}"}'
+    assert responses.calls[0].request.body == '{"trusted_profile": {"id": "iam-id-123"}}'
     assert responses.calls[0].request.params['version'] == '2021-09-20'
     # Check the logs.
     #pylint: disable=line-too-long

From 2af69938e09ee65df84119f2b106d0cf8b79aa83 Mon Sep 17 00:00:00 2001
From: Norbert Biczo <pyrooka@users.noreply.github.com>
Date: Fri, 29 Oct 2021 11:53:27 +0200
Subject: [PATCH 6/8] chore: address issues 2

Co-authored-by: Phil Adams <phil_adams@us.ibm.com>
---
 Authentication.md                             |  6 ++--
 .../vpc_instance_token_manager.py             |  3 +-
 test/test_vpc_instance_token_manager.py       | 31 +++++++++++++++++++
 3 files changed, 36 insertions(+), 4 deletions(-)

diff --git a/Authentication.md b/Authentication.md
index c01db710..261d0d19 100644
--- a/Authentication.md
+++ b/Authentication.md
@@ -3,8 +3,8 @@ The python-sdk-core project supports the following types of authentication:
 - Basic Authentication
 - Bearer Token Authentication
 - Identity and Access Management (IAM) Authentication
-- VPC Instance Authentication
 - Container Authentication
+- VPC Instance Authentication
 - Cloud Pak for Data Authentication
 - No Authentication
 
@@ -295,7 +295,7 @@ service = ExampleServiceV1.new_instance(service_name='example_service')
 
 
 ## VPC Instance Authentication
-The `VpcInstanceAuthenticator` is intended to be used by application code
+The `VPCInstanceAuthenticator` is intended to be used by application code
 running inside a VPC-managed compute resource (virtual server instance) that has been configured
 to use the "compute resource identity" feature.
 The compute resource identity feature allows you to assign a trusted IAM profile to the compute resource as its "identity".
@@ -305,7 +305,7 @@ This results in a simplified security model that allows the application develope
 - avoid storing credentials in application code, configuraton files or a password vault
 - avoid managing or rotating credentials
 
-The `VpcInstanceAuthenticator` will invoke the appropriate operations on the compute resource's locally-available
+The `VPCInstanceAuthenticator` will invoke the appropriate operations on the compute resource's locally-available
 VPC Instance Metadata Service to (1) retrieve an instance identity token
 and then (2) exchange that instance identity token for an IAM access token.
 The authenticator will repeat these steps to obtain a new IAM access token whenever the current access token expires.
diff --git a/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py b/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py
index 173be489..2a671361 100644
--- a/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py
+++ b/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py
@@ -79,6 +79,7 @@ def request_token(self) -> dict:
 
         url = self.url + '/instance_identity/v1/iam_token'
 
+        request_payload = None
         if self.iam_profile_crn:
             request_payload = {'trusted_profile': {'crn': self.iam_profile_crn}}
         if self.iam_profile_id:
@@ -97,7 +98,7 @@ def request_token(self) -> dict:
             url=url,
             headers=headers,
             params={'version': self.METADATA_SERVICE_VERSION},
-            data=json.dumps(request_payload))
+            data=json.dumps(request_payload) if request_payload else None)
         logging.debug('Returned from VPC \'create_iam_token\' operation."')
 
         return response
diff --git a/test/test_vpc_instance_token_manager.py b/test/test_vpc_instance_token_manager.py
index 76bceb21..be681f70 100644
--- a/test/test_vpc_instance_token_manager.py
+++ b/test/test_vpc_instance_token_manager.py
@@ -179,6 +179,37 @@ def mock_retrieve_instance_identity_token():
     assert caplog.record_tuples[1][2] == 'Returned from VPC \'create_iam_token\' operation."'
 
 
+@responses.activate
+def test_request_token(caplog):
+    caplog.set_level(logging.DEBUG)
+
+    token_manager = VPCInstanceTokenManager()
+
+    # Mock the retrieve instance identity token method.
+    def mock_retrieve_instance_identity_token():
+        return TEST_TOKEN
+    token_manager.retrieve_instance_identity_token = mock_retrieve_instance_identity_token
+
+    response = {
+        'access_token': TEST_IAM_TOKEN,
+    }
+
+    responses.add(responses.POST, 'http://169.254.169.254/instance_identity/v1/iam_token',
+                  body=json.dumps(response), status=200)
+
+    response = token_manager.request_token()
+    assert len(responses.calls) == 1
+    assert responses.calls[0].request.headers['Content-Type'] == 'application/json'
+    assert responses.calls[0].request.headers['Accept'] == 'application/json'
+    assert responses.calls[0].request.headers['Authorization'] == 'Bearer ' + TEST_TOKEN
+    assert responses.calls[0].request.body is None
+    assert responses.calls[0].request.params['version'] == '2021-09-20'
+    # Check the logs.
+    #pylint: disable=line-too-long
+    assert caplog.record_tuples[0][2] == 'Invoking VPC \'create_iam_token\' operation: http://169.254.169.254/instance_identity/v1/iam_token'
+    assert caplog.record_tuples[1][2] == 'Returned from VPC \'create_iam_token\' operation."'
+
+
 @responses.activate
 def test_request_token_failed(caplog):
     caplog.set_level(logging.DEBUG)

From 7d5f4983376d6ebd4c0b580f6720c2a20c016069 Mon Sep 17 00:00:00 2001
From: Norbert Biczo <pyrooka@users.noreply.github.com>
Date: Fri, 29 Oct 2021 13:42:11 +0200
Subject: [PATCH 7/8] chore: update .secrets.baseline

---
 .secrets.baseline | 26 ++++++++++++++++++--------
 1 file changed, 18 insertions(+), 8 deletions(-)

diff --git a/.secrets.baseline b/.secrets.baseline
index 002a8772..881fb3d9 100644
--- a/.secrets.baseline
+++ b/.secrets.baseline
@@ -3,7 +3,7 @@
     "files": "package-lock.json|^.secrets.baseline$",
     "lines": null
   },
-  "generated_at": "2021-10-15T20:17:29Z",
+  "generated_at": "2021-10-29T11:41:22Z",
   "plugins_used": [
     {
       "name": "AWSKeyDetector"
@@ -70,7 +70,7 @@
         "hashed_secret": "91dfd9ddb4198affc5c194cd8ce6d338fde470e2",
         "is_secret": false,
         "is_verified": false,
-        "line_number": 66,
+        "line_number": 64,
         "type": "Secret Keyword",
         "verified_result": null
       },
@@ -78,7 +78,7 @@
         "hashed_secret": "98635b2eaa2379f28cd6d72a38299f286b81b459",
         "is_secret": false,
         "is_verified": false,
-        "line_number": 297,
+        "line_number": 384,
         "type": "Secret Keyword",
         "verified_result": null
       },
@@ -86,7 +86,7 @@
         "hashed_secret": "47fcf185ee7e15fe05cae31fbe9e4ebe4a06a40d",
         "is_secret": false,
         "is_verified": false,
-        "line_number": 328,
+        "line_number": 415,
         "type": "Secret Keyword",
         "verified_result": null
       }
@@ -96,7 +96,7 @@
         "hashed_secret": "fdee05598fdd57ff8e9ae29e92c25a04f2c52fa6",
         "is_secret": false,
         "is_verified": false,
-        "line_number": 29,
+        "line_number": 30,
         "type": "Secret Keyword",
         "verified_result": null
       }
@@ -346,7 +346,7 @@
         "hashed_secret": "34a0a47a51d5bf739df0214450385e29ee7e9847",
         "is_secret": false,
         "is_verified": false,
-        "line_number": 417,
+        "line_number": 447,
         "type": "Secret Keyword",
         "verified_result": null
       },
@@ -354,7 +354,7 @@
         "hashed_secret": "2863fa4b5510c46afc2bd2998dfbc0cf3d6df032",
         "is_secret": false,
         "is_verified": false,
-        "line_number": 497,
+        "line_number": 527,
         "type": "Secret Keyword",
         "verified_result": null
       },
@@ -362,10 +362,20 @@
         "hashed_secret": "b9cad336062c0dc3bb30145b1a6697fccfe755a6",
         "is_secret": false,
         "is_verified": false,
-        "line_number": 558,
+        "line_number": 588,
         "type": "Secret Keyword",
         "verified_result": null
       }
+    ],
+    "test/test_vpc_instance_token_manager.py": [
+      {
+        "hashed_secret": "c8f0df25bade89c1873f5f01b85bcfb921443ac6",
+        "is_secret": false,
+        "is_verified": false,
+        "line_number": 28,
+        "type": "JSON Web Token",
+        "verified_result": null
+      }
     ]
   },
   "version": "0.13.1+ibm.46.dss",

From 7f0f6f24135f1ccaae6c865389eff3d1feff267e Mon Sep 17 00:00:00 2001
From: Norbert Biczo <pyrooka@users.noreply.github.com>
Date: Tue, 2 Nov 2021 10:51:48 +0100
Subject: [PATCH 8/8] chore: fix typos

Co-authored-by: Dustin Popp <dustinpopp@ibm.com>
---
 .../authenticators/vpc_instance_authenticator.py              | 2 +-
 ibm_cloud_sdk_core/get_authenticator.py                       | 4 ++--
 .../token_managers/vpc_instance_token_manager.py              | 4 ++--
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/ibm_cloud_sdk_core/authenticators/vpc_instance_authenticator.py b/ibm_cloud_sdk_core/authenticators/vpc_instance_authenticator.py
index 4893f873..82ca50f3 100644
--- a/ibm_cloud_sdk_core/authenticators/vpc_instance_authenticator.py
+++ b/ibm_cloud_sdk_core/authenticators/vpc_instance_authenticator.py
@@ -36,7 +36,7 @@ class VPCInstanceAuthenticator(Authenticator):
     Keyword Arguments:
         iam_profile_crn (str, optional):
             The CRN of the linked trusted IAM profile to be used as the identity of the compute resource.
-            At most one of iamProfileCrn or iamProfileId may be specified. If neither one is specified,
+            At most one of iam_profile_crn or iam_profile_id may be specified. If neither one is specified,
             then the default IAM profile defined for the compute resource will be used. Defaults to None.
         iam_profile_id (str, optional):
             The ID of the linked trusted IAM profile to be used when obtaining the IAM access token.
diff --git a/ibm_cloud_sdk_core/get_authenticator.py b/ibm_cloud_sdk_core/get_authenticator.py
index f9742afe..8b8bcb17 100644
--- a/ibm_cloud_sdk_core/get_authenticator.py
+++ b/ibm_cloud_sdk_core/get_authenticator.py
@@ -14,9 +14,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from ibm_cloud_sdk_core.authenticators.vpc_instance_authenticator import VPCInstanceAuthenticator
 from .authenticators import (Authenticator, BasicAuthenticator, BearerTokenAuthenticator, ContainerAuthenticator,
-                             CloudPakForDataAuthenticator, IAMAuthenticator, NoAuthAuthenticator)
+                             CloudPakForDataAuthenticator, IAMAuthenticator, NoAuthAuthenticator,
+                             VPCInstanceAuthenticator)
 from .utils import read_external_sources
 
 
diff --git a/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py b/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py
index 2a671361..7bb8300c 100644
--- a/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py
+++ b/ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py
@@ -34,11 +34,11 @@ class VPCInstanceTokenManager(JWTTokenManager):
     Keyword Arguments:
         iam_profile_crn (str, optional):
             The CRN of the linked trusted IAM profile to be used as the identity of the compute resource.
-            At most one of iamProfileCrn or iamProfileId may be specified. If neither one is specified,
+            At most one of iam_profile_crn or iam_profile_id may be specified. If neither one is specified,
             then the default IAM profile defined for the compute resource will be used. Defaults to None.
         iam_profile_id (str, optional):
             The ID of the linked trusted IAM profile to be used when obtaining the IAM access token.
-            At most one of iamProfileCrn or iamProfileId may be specified. If neither one is specified,
+            At most one of iam_profile_crn or iam_profile_id may be specified. If neither one is specified,
             then the default IAM profile defined for the compute resource will be used. Defaults to None.
         url (str, optional):
             The VPC Instance Metadata Service's base endpoint URL. Defaults to 'http://169.254.169.254'.
