Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(VPCInstanceAuthenticator): add support for new VPC authentication flow #129

Merged
merged 9 commits into from
Nov 8, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 74 additions & 7 deletions Authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
pyrooka marked this conversation as resolved.
Show resolved Hide resolved
- Container Authentication
- Cloud Pak for Data Authentication
- No Authentication
Expand All @@ -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:
Expand Down Expand Up @@ -296,6 +294,76 @@ service = ExampleServiceV1.new_instance(service_name='example_service')
```


## VPC Instance Authentication
The `VpcInstanceAuthenticator` is intended to be used by application code
pyrooka marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down Expand Up @@ -392,4 +460,3 @@ from <sdk-package-name>.example_service_v1 import *
service = ExampleServiceV1.new_instance(service_name='example_service')

# 'service' can now be used to invoke operations.
```
1 change: 1 addition & 0 deletions ibm_cloud_sdk_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions ibm_cloud_sdk_core/authenticators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions ibm_cloud_sdk_core/authenticators/authenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down
124 changes: 124 additions & 0 deletions ibm_cloud_sdk_core/authenticators/vpc_instance_authenticator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# 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 ..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,
pyrooka marked this conversation as resolved.
Show resolved Hide resolved
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,
pyrooka marked this conversation as resolved.
Show resolved Hide resolved
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.
"""

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] = 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)

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()
6 changes: 6 additions & 0 deletions ibm_cloud_sdk_core/get_authenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
pyrooka marked this conversation as resolved.
Show resolved Hide resolved
from .authenticators import (Authenticator, BasicAuthenticator, BearerTokenAuthenticator, ContainerAuthenticator,
CloudPakForDataAuthenticator, IAMAuthenticator, NoAuthAuthenticator)
from .utils import read_external_sources
Expand Down Expand Up @@ -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()

Expand Down
Loading