Skip to content

Commit 5cb1c21

Browse files
authored
feat(VPCInstanceAuthenticator): add support for new VPC authentication flow (#129)
This commit introduces the VPCInstanceAuthenticator. This authenticator implements the authentication flow within a VPC-managed compute resource that is configured to use the compute resource identity feature. This involves the use of the compute resource's local VPC Instance Metadata Service API to retrieve an instance identity token, and then exchange that token for an IAM access token. The IAM access token is then used to authenticate outbound REST API requests by adding an Authorization containing the access token.
1 parent 8bbeca9 commit 5cb1c21

12 files changed

+751
-16
lines changed

.secrets.baseline

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"files": "package-lock.json|^.secrets.baseline$",
44
"lines": null
55
},
6-
"generated_at": "2021-10-15T20:17:29Z",
6+
"generated_at": "2021-10-29T11:41:22Z",
77
"plugins_used": [
88
{
99
"name": "AWSKeyDetector"
@@ -70,23 +70,23 @@
7070
"hashed_secret": "91dfd9ddb4198affc5c194cd8ce6d338fde470e2",
7171
"is_secret": false,
7272
"is_verified": false,
73-
"line_number": 66,
73+
"line_number": 64,
7474
"type": "Secret Keyword",
7575
"verified_result": null
7676
},
7777
{
7878
"hashed_secret": "98635b2eaa2379f28cd6d72a38299f286b81b459",
7979
"is_secret": false,
8080
"is_verified": false,
81-
"line_number": 297,
81+
"line_number": 384,
8282
"type": "Secret Keyword",
8383
"verified_result": null
8484
},
8585
{
8686
"hashed_secret": "47fcf185ee7e15fe05cae31fbe9e4ebe4a06a40d",
8787
"is_secret": false,
8888
"is_verified": false,
89-
"line_number": 328,
89+
"line_number": 415,
9090
"type": "Secret Keyword",
9191
"verified_result": null
9292
}
@@ -96,7 +96,7 @@
9696
"hashed_secret": "fdee05598fdd57ff8e9ae29e92c25a04f2c52fa6",
9797
"is_secret": false,
9898
"is_verified": false,
99-
"line_number": 29,
99+
"line_number": 30,
100100
"type": "Secret Keyword",
101101
"verified_result": null
102102
}
@@ -346,26 +346,36 @@
346346
"hashed_secret": "34a0a47a51d5bf739df0214450385e29ee7e9847",
347347
"is_secret": false,
348348
"is_verified": false,
349-
"line_number": 417,
349+
"line_number": 447,
350350
"type": "Secret Keyword",
351351
"verified_result": null
352352
},
353353
{
354354
"hashed_secret": "2863fa4b5510c46afc2bd2998dfbc0cf3d6df032",
355355
"is_secret": false,
356356
"is_verified": false,
357-
"line_number": 497,
357+
"line_number": 527,
358358
"type": "Secret Keyword",
359359
"verified_result": null
360360
},
361361
{
362362
"hashed_secret": "b9cad336062c0dc3bb30145b1a6697fccfe755a6",
363363
"is_secret": false,
364364
"is_verified": false,
365-
"line_number": 558,
365+
"line_number": 588,
366366
"type": "Secret Keyword",
367367
"verified_result": null
368368
}
369+
],
370+
"test/test_vpc_instance_token_manager.py": [
371+
{
372+
"hashed_secret": "c8f0df25bade89c1873f5f01b85bcfb921443ac6",
373+
"is_secret": false,
374+
"is_verified": false,
375+
"line_number": 28,
376+
"type": "JSON Web Token",
377+
"verified_result": null
378+
}
369379
]
370380
},
371381
"version": "0.13.1+ibm.46.dss",

Authentication.md

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ The python-sdk-core project supports the following types of authentication:
44
- Bearer Token Authentication
55
- Identity and Access Management (IAM) Authentication
66
- Container Authentication
7+
- VPC Instance Authentication
78
- Cloud Pak for Data Authentication
89
- No Authentication
910

@@ -13,13 +14,10 @@ so it is important for the SDK user to consult with the appropriate service docu
1314
to understand which authenticators are supported for that service.
1415

1516
The python-sdk-core allows an authenticator to be specified in one of two ways:
16-
1. programmatically - the SDK user invokes the appropriate function(s) to create an instance of the
17-
desired authenticator and then passes the authenticator instance when constructing an instance of the service client.
17+
1. programmatically - the SDK user invokes the appropriate function(s) to create an instance of the
18+
desired authenticator and then passes the authenticator instance when constructing an instance of the service.
1819
2. configuration - the SDK user provides external configuration information (in the form of environment variables
19-
or a credentials file) to indicate the type of authenticator, along with the configuration of the necessary properties
20-
for that authenticator.
21-
The SDK user then invokes the configuration-based service client constructor method
22-
to construct an instance of the authenticator and service client that reflect the external configuration information.
20+
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.
2321

2422
The sections below will provide detailed information for each authenticator
2523
which will include the following:
@@ -296,6 +294,76 @@ service = ExampleServiceV1.new_instance(service_name='example_service')
296294
```
297295

298296

297+
## VPC Instance Authentication
298+
The `VPCInstanceAuthenticator` is intended to be used by application code
299+
running inside a VPC-managed compute resource (virtual server instance) that has been configured
300+
to use the "compute resource identity" feature.
301+
The compute resource identity feature allows you to assign a trusted IAM profile to the compute resource as its "identity".
302+
This, in turn, allows applications running within the compute resource to take on this identity when interacting with
303+
IAM-secured IBM Cloud services.
304+
This results in a simplified security model that allows the application developer to:
305+
- avoid storing credentials in application code, configuraton files or a password vault
306+
- avoid managing or rotating credentials
307+
308+
The `VPCInstanceAuthenticator` will invoke the appropriate operations on the compute resource's locally-available
309+
VPC Instance Metadata Service to (1) retrieve an instance identity token
310+
and then (2) exchange that instance identity token for an IAM access token.
311+
The authenticator will repeat these steps to obtain a new IAM access token whenever the current access token expires.
312+
The IAM access token is added to each outbound request in the `Authorization` header in the form:
313+
```
314+
Authorization: Bearer <IAM-access-token>
315+
```
316+
317+
### Properties
318+
319+
- iam_profile_crn: (optional) the crn of the linked trusted IAM profile to be used when obtaining the IAM access token.
320+
321+
- iam_profile_id: (optional) the id of the linked trusted IAM profile to be used when obtaining the IAM access token.
322+
323+
- url: (optional) The VPC Instance Metadata Service's base URL.
324+
The default value of this property is `http://169.254.169.254`, and should not need to be specified in normal situations.
325+
326+
Usage Notes:
327+
1. At most one of `iam_profile_crn` or `iam_profile_id` may be specified. The specified value must map
328+
to a trusted IAM profile that has been linked to the compute resource (virtual server instance).
329+
330+
2. If both `iam_profile_crn` and `iam_profile_id` are specified, then an error occurs.
331+
332+
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.
333+
If no default trusted profile is defined for the compute resource, then an error occurs.
334+
335+
336+
### Programming example
337+
```python
338+
from ibm_cloud_sdk_core.authenticators import VPCInstanceAuthenticator
339+
from <sdk-package-name>.example_service_v1 import *
340+
341+
# Create the authenticator.
342+
authenticator = VPCInstanceAuthenticator(iam_profile_crn='crn:iam-profile-123')
343+
344+
# Construct the service instance.
345+
service = ExampleServiceV1(authenticator=authenticator)
346+
347+
# 'service' can now be used to invoke operations.
348+
```
349+
350+
### Configuration example
351+
External configuration:
352+
```
353+
export EXAMPLE_SERVICE_AUTH_TYPE=vpc
354+
export EXAMPLE_SERVICE_IAM_PROFILE_CRN=crn:iam-profile-123
355+
```
356+
Application code:
357+
```python
358+
from <sdk-package-name>.example_service_v1 import *
359+
360+
# Construct the service instance.
361+
service = ExampleServiceV1.new_instance(service_name='example_service')
362+
363+
# 'service' can now be used to invoke operations.
364+
```
365+
366+
299367
## Cloud Pak for Data
300368
The `CloudPakForDataAuthenticator` will accept a user-supplied username value, along with either a
301369
password or apikey, and will
@@ -392,4 +460,3 @@ from <sdk-package-name>.example_service_v1 import *
392460
service = ExampleServiceV1.new_instance(service_name='example_service')
393461

394462
# 'service' can now be used to invoke operations.
395-
```

ibm_cloud_sdk_core/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from .token_managers.jwt_token_manager import JWTTokenManager
4343
from .token_managers.cp4d_token_manager import CP4DTokenManager
4444
from .token_managers.container_token_manager import ContainerTokenManager
45+
from .token_managers.vpc_instance_token_manager import VPCInstanceTokenManager
4546
from .api_exception import ApiException
4647
from .utils import datetime_to_string, string_to_datetime, read_external_sources
4748
from .utils import datetime_to_string_list, string_to_datetime_list

ibm_cloud_sdk_core/authenticators/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,5 @@
3939
from .container_authenticator import ContainerAuthenticator
4040
from .cp4d_authenticator import CloudPakForDataAuthenticator
4141
from .iam_authenticator import IAMAuthenticator
42+
from .vpc_instance_authenticator import VPCInstanceAuthenticator
4243
from .no_auth_authenticator import NoAuthAuthenticator

ibm_cloud_sdk_core/authenticators/authenticator.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class Authenticator(ABC):
2626
AUTHTYPE_IAM = 'iam'
2727
AUTHTYPE_CONTAINER = 'container'
2828
AUTHTYPE_CP4D = 'cp4d'
29+
AUTHTYPE_VPC = 'vpc'
2930
AUTHTYPE_NOAUTH = 'noAuth'
3031
AUTHTYPE_UNKNOWN = 'unknown'
3132

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# coding: utf-8
2+
3+
# Copyright 2021 IBM All Rights Reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
from typing import Optional
18+
19+
from requests import Request
20+
21+
from ..token_managers.vpc_instance_token_manager import VPCInstanceTokenManager
22+
from .authenticator import Authenticator
23+
24+
25+
class VPCInstanceAuthenticator(Authenticator):
26+
"""VPCInstanceAuthenticator implements an authentication scheme in which it
27+
retrieves an "instance identity token" and exchanges that
28+
for an IAM access token using the VPC Instance Metadata Service API which is available
29+
on the local compute resource (VM).
30+
The instance identity token is similar to an IAM apikey, except that it is managed
31+
automatically by the compute resource provider (VPC).
32+
The resulting IAM access token is then added to outbound requests in an Authorization header of the form:
33+
34+
Authorization: Bearer <access-token>
35+
36+
Keyword Arguments:
37+
iam_profile_crn (str, optional):
38+
The CRN of the linked trusted IAM profile to be used as the identity of the compute resource.
39+
At most one of iam_profile_crn or iam_profile_id may be specified. If neither one is specified,
40+
then the default IAM profile defined for the compute resource will be used. Defaults to None.
41+
iam_profile_id (str, optional):
42+
The ID of the linked trusted IAM profile to be used when obtaining the IAM access token.
43+
At most one of iamProfileCrn or iamProfileId may be specified. If neither one is specified,
44+
then the default IAM profile defined for the compute resource will be used. Defaults to None.
45+
url (str, optional):
46+
The VPC Instance Metadata Service's base endpoint URL. Defaults to 'http://169.254.169.254'.
47+
48+
Attributes:
49+
iam_profile_crn (str, optional): The CRN of the linked trusted IAM profile.
50+
iam_profile_id (str, optional): The ID of the linked trusted IAM profile.
51+
url (str, optional): The VPC Instance Metadata Service's base endpoint URL.
52+
"""
53+
54+
DEFAULT_IMS_ENDPOINT = 'http://169.254.169.254'
55+
56+
def __init__(self,
57+
iam_profile_crn: Optional[str] = None,
58+
iam_profile_id: Optional[str] = None,
59+
url: Optional[str] = None) -> None:
60+
61+
if not url:
62+
url = self.DEFAULT_IMS_ENDPOINT
63+
64+
self.token_manager = VPCInstanceTokenManager(
65+
url=url, iam_profile_crn=iam_profile_crn, iam_profile_id=iam_profile_id)
66+
67+
self.validate()
68+
69+
def authentication_type(self) -> str:
70+
"""Returns this authenticator's type ('VPC')."""
71+
return Authenticator.AUTHTYPE_VPC
72+
73+
def validate(self) -> None:
74+
super().validate()
75+
76+
if self.token_manager.iam_profile_crn and self.token_manager.iam_profile_id:
77+
raise ValueError(
78+
'At most one of "iam_profile_id" or "iam_profile_crn" may be specified.')
79+
80+
def authenticate(self, req: Request) -> None:
81+
"""Adds IAM authentication information to the request.
82+
83+
The IAM access token will be added to the request's headers in the form:
84+
85+
Authorization: Bearer <bearer-token>
86+
87+
Args:
88+
req: The request to add IAM authentication information too. Must contain a key to a dictionary
89+
called headers.
90+
"""
91+
headers = req.get('headers')
92+
bearer_token = self.token_manager.get_token()
93+
headers['Authorization'] = 'Bearer {0}'.format(bearer_token)
94+
95+
96+
def set_iam_profile_crn(self, iam_profile_crn: str) -> None:
97+
"""Sets CRN of the IAM profile.
98+
99+
Args:
100+
iam_profile_crn (str): the CRN of the linked trusted IAM profile to be used as
101+
the identity of the compute resource.
102+
103+
Raises:
104+
ValueError: At most one of iam_profile_crn or iam_profile_id may be specified.
105+
If neither one is specified, then the default IAM profile defined
106+
for the compute resource will be used.
107+
"""
108+
self.token_manager.set_iam_profile_crn(iam_profile_crn)
109+
self.validate()
110+
111+
def set_iam_profile_id(self, iam_profile_id: str) -> None:
112+
"""Sets the ID of the IAM profile.
113+
114+
Args:
115+
iam_profile_id (str): id of the linked trusted IAM profile to be used when obtaining
116+
the IAM access token
117+
118+
Raises:
119+
ValueError: At most one of iam_profile_crn or iam_profile_id may be specified.
120+
If neither one is specified, then the default IAM profile defined
121+
for the compute resource will be used.
122+
"""
123+
self.token_manager.set_iam_profile_id(iam_profile_id)
124+
self.validate()

ibm_cloud_sdk_core/get_authenticator.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
# limitations under the License.
1616

1717
from .authenticators import (Authenticator, BasicAuthenticator, BearerTokenAuthenticator, ContainerAuthenticator,
18-
CloudPakForDataAuthenticator, IAMAuthenticator, NoAuthAuthenticator)
18+
CloudPakForDataAuthenticator, IAMAuthenticator, NoAuthAuthenticator,
19+
VPCInstanceAuthenticator)
1920
from .utils import read_external_sources
2021

2122

@@ -91,6 +92,11 @@ def __construct_authenticator(config: dict) -> Authenticator:
9192
disable_ssl_verification=config.get(
9293
'AUTH_DISABLE_SSL', 'false').lower() == 'true',
9394
scope=config.get('SCOPE'))
95+
elif auth_type == Authenticator.AUTHTYPE_VPC.lower():
96+
authenticator = VPCInstanceAuthenticator(
97+
iam_profile_crn=config.get('IAM_PROFILE_CRN'),
98+
iam_profile_id=config.get('IAM_PROFILE_ID'),
99+
url=config.get('AUTH_URL'))
94100
elif auth_type == Authenticator.AUTHTYPE_NOAUTH.lower():
95101
authenticator = NoAuthAuthenticator()
96102

0 commit comments

Comments
 (0)