Skip to content

Commit 0d9a1a0

Browse files
committed
feat: add container authenticator
1 parent 9b73630 commit 0d9a1a0

File tree

5 files changed

+310
-2
lines changed

5 files changed

+310
-2
lines changed

ibm_cloud_sdk_core/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from .token_managers.iam_token_manager import IAMTokenManager
4242
from .token_managers.jwt_token_manager import JWTTokenManager
4343
from .token_managers.cp4d_token_manager import CP4DTokenManager
44+
from .token_managers.container_token_manager import ContainerTokenManager
4445
from .api_exception import ApiException
4546
from .utils import datetime_to_string, string_to_datetime, read_external_sources
4647
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
@@ -36,6 +36,7 @@
3636
from .authenticator import Authenticator
3737
from .basic_authenticator import BasicAuthenticator
3838
from .bearer_token_authenticator import BearerTokenAuthenticator
39+
from .container_authenticator import ContainerAuthenticator
3940
from .cp4d_authenticator import CloudPakForDataAuthenticator
4041
from .iam_authenticator import IAMAuthenticator
4142
from .no_auth_authenticator import NoAuthAuthenticator
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
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 Dict, Optional
18+
19+
from .iam_request_based_authenticator import IAMRequestBasedAuthenticator
20+
from ..token_managers.container_token_manager import ContainerTokenManager
21+
22+
23+
class ContainerAuthenticator(IAMRequestBasedAuthenticator):
24+
"""ContainerAuthenticator implements an IAM-based authentication schema where by it
25+
retrieves a "compute resource token" from the local compute resource (VM)
26+
and uses that to obtain an IAM access token by invoking the IAM "get token" operation with grant-type=cr-token.
27+
The resulting IAM access token is then added to outbound requests in an Authorization header of the form:
28+
29+
Authorization: Bearer <access-token>
30+
31+
Args:
32+
cr_token_filename: The name of the file containing the injected CR token value
33+
(applies to IKS-managed compute resources). Defaults to "/var/run/secrets/tokens/vault-token".
34+
iam_profile_name: The name of the linked trusted IAM profile to be used when obtaining the IAM access token
35+
(a CR token might map to multiple IAM profiles). One of IAMProfileName or IAMProfileID must be specified.
36+
sDefaults to None.
37+
iam_profile_id: The id of the linked trusted IAM profile to be used when obtaining the IAM access token
38+
(a CR token might map to multiple IAM profiles). One of IAMProfileName or IAMProfileID must be specified.
39+
Defaults to None.
40+
url: The URL representing the IAM token service endpoint. If not specified, a suitable default value is used.
41+
client_id: The client_id and client_secret fields are used to form
42+
a "basic" authorization header for IAM token requests. Defaults to None.
43+
client_secret: The client_id and client_secret fields are used to form
44+
a "basic" authorization header for IAM token requests. Defaults to None.
45+
disable_ssl_verification: A flag that indicates whether verification of
46+
the server's SSL certificate should be disabled or not. Defaults to False.
47+
headers: Default headers to be sent with every IAM token request. Defaults to None.
48+
proxies: Dictionary for mapping request protocol to proxy URL. Defaults to None.
49+
proxies.http (optional): The proxy endpoint to use for HTTP requests.
50+
proxies.https (optional): The proxy endpoint to use for HTTPS requests.
51+
scope: The "scope" to use when fetching the bearer token from the IAM token server.
52+
This can be used to obtain an access token with a specific scope.
53+
54+
Attributes:
55+
token_manager (ContainerTokenManager): Retrives and manages IAM tokens
56+
from the endpoint specified by the url.
57+
58+
Raises:
59+
ValueError: Neither of iam_profile_name or iam_profile_idk are set,
60+
or client_id, and/or client_secret are not valid for IAM token requests.
61+
"""
62+
authentication_type = 'container'
63+
64+
def __init__(self,
65+
cr_token_filename: Optional[str] = None,
66+
iam_profile_name: Optional[str] = None,
67+
iam_profile_id: Optional[str] = None,
68+
url: Optional[str] = None,
69+
client_id: Optional[str] = None,
70+
client_secret: Optional[str] = None,
71+
disable_ssl_verification: bool = False,
72+
scope: Optional[str] = None,
73+
proxies: Optional[Dict[str, str]] = None,
74+
headers: Optional[Dict[str, str]] = None) -> None:
75+
self.token_manager = ContainerTokenManager(
76+
cr_token_filename=cr_token_filename, iam_profile_name=iam_profile_name, iam_profile_id=iam_profile_id,
77+
url=url, client_id=client_id, client_secret=client_secret,
78+
disable_ssl_verification=disable_ssl_verification, scope=scope, proxies=proxies, headers=headers)
79+
self.validate()
80+
81+
def validate(self) -> None:
82+
"""Validates the iam_profile_name, iam_profile_id, client_id, and client_secret for IAM token requests.
83+
84+
Ensure that one of the iam_profile_name or iam_profile_id are specified. Additionally, ensure
85+
both of the client_id and client_secret are set if either of them are defined.
86+
87+
Raises:
88+
ValueError: Neither of iam_profile_name or iam_profile_idk are set,
89+
or client_id, and/or client_secret are not valid for IAM token requests.
90+
"""
91+
super().validate()
92+
93+
if not self.token_manager.iam_profile_name and not self.token_manager.iam_profile_id:
94+
raise ValueError('At least one of iam_profile_name or iam_profile_id must be specified.')
95+
96+
def set_cr_token_filename(self, cr_token_filename: str) -> None:
97+
"""Set the location of the compute resource token on the local filesystem.
98+
99+
Args:
100+
cr_token_filename: path to the compute resource token
101+
"""
102+
self.token_manager.cr_token_filename = cr_token_filename
103+
104+
def set_iam_profile_name(self, iam_profile_name: str) -> None:
105+
"""Set the name of the IAM profile.
106+
107+
Args:
108+
iam_profile_name: name of the linked trusted IAM profile to be used when obtaining the IAM access token
109+
110+
Raises:
111+
ValueError: Neither of iam_profile_name or iam_profile_idk are set,
112+
or client_id, and/or client_secret are not valid for IAM token requests.
113+
"""
114+
self.token_manager.iam_profile_name = iam_profile_name
115+
self.validate()
116+
117+
def set_iam_profile_id(self, iam_profile_id: str) -> None:
118+
"""Set the id of the IAM profile.
119+
120+
Args:
121+
iam_profile_id: id of the linked trusted IAM profile to be used when obtaining the IAM access token
122+
123+
Raises:
124+
ValueError: Neither of iam_profile_name or iam_profile_idk are set,
125+
or client_id, and/or client_secret are not valid for IAM token requests.
126+
"""
127+
self.token_manager.iam_profile_id = iam_profile_id
128+
self.validate()

ibm_cloud_sdk_core/get_authenticator.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
# See the License for the specific language governing permissions and
1515
# limitations under the License.
1616

17-
from .authenticators import (Authenticator, BasicAuthenticator, BearerTokenAuthenticator,
17+
from .authenticators import (Authenticator, BasicAuthenticator, BearerTokenAuthenticator, ContainerAuthenticator,
1818
CloudPakForDataAuthenticator, IAMAuthenticator, NoAuthAuthenticator)
1919
from .utils import read_external_sources
2020

@@ -41,7 +41,13 @@ def get_authenticator_from_environment(service_name: str) -> Authenticator:
4141

4242

4343
def __construct_authenticator(config: dict) -> Authenticator:
44-
auth_type = config.get('AUTH_TYPE').lower() if config.get('AUTH_TYPE') else 'iam'
44+
# Determine the authentication type if not specified explicitly.
45+
if config.get('AUTH_TYPE'):
46+
auth_type = config.get('AUTH_TYPE').lower()
47+
else:
48+
# If the APIKEY property is specified, then it should be IAM, otherwise Container Auth.
49+
auth_type = 'iam' if config.get('APIKEY') else 'container'
50+
4551
authenticator = None
4652

4753
if auth_type == 'basic':
@@ -51,6 +57,16 @@ def __construct_authenticator(config: dict) -> Authenticator:
5157
elif auth_type == 'bearertoken':
5258
authenticator = BearerTokenAuthenticator(
5359
bearer_token=config.get('BEARER_TOKEN'))
60+
elif auth_type == 'container':
61+
authenticator = ContainerAuthenticator(
62+
cr_token_filename=config.get('CR_TOKEN_FILENAME'),
63+
iam_profile_name=config.get('IAM_PROFILE_NAME'),
64+
iam_profile_id=config.get('IAM_PROFILE_ID'),
65+
url=config.get('AUTH_URL'),
66+
client_id=config.get('CLIENT_ID'),
67+
client_secret=config.get('CLIENT_SECRET'),
68+
disable_ssl_verification=config.get('AUTH_DISABLE_SSL'),
69+
scope=config.get('SCOPE'))
5470
elif auth_type == 'cp4d':
5571
authenticator = CloudPakForDataAuthenticator(
5672
username=config.get('USERNAME'),
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
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+
import logging
18+
from typing import Dict, Optional
19+
20+
from .iam_request_based_token_manager import IAMRequestBasedTokenManager
21+
22+
23+
class ContainerTokenManager(IAMRequestBasedTokenManager):
24+
"""The ContainerTokenManager takes a compute resource token and performs the necessary interactions with
25+
the IAM token service to obtain and store a suitable bearer token. Additionally, the ContainerTokenManager
26+
will retrieve bearer tokens via basic auth using a supplied client_id and client_secret pair.
27+
28+
If the current stored bearer token has expired a new bearer token will be retrieved.
29+
30+
Attributes:
31+
container_token_filename(str): The name of the file containing the injected CR token value
32+
(applies to IKS-managed compute resources).
33+
iam_profile_name (str): The name of the linked trusted IAM profile to be used when obtaining the
34+
IAM access token (a CR token might map to multiple IAM profiles).
35+
One of IAMProfileName or IAMProfileID must be specified.
36+
iam_profile_id (str): The id of the linked trusted IAM profile to be used when obtaining the IAM access token
37+
(a CR token might map to multiple IAM profiles). One of IAMProfileName or IAMProfileID must be specified.
38+
url (str): The IAM endpoint to token requests.
39+
client_id (str): The client_id and client_secret fields are used to form
40+
a "basic auth" Authorization header for interactions with the IAM token server.
41+
client_secret (str): The client_id and client_secret fields are used to form
42+
a "basic auth" Authorization header for interactions with the IAM token server.
43+
headers (dict): Default headers to be sent with every IAM token request.
44+
proxies (dict): Proxies to use for communicating with IAM.
45+
proxies.http (str): The proxy endpoint to use for HTTP requests.
46+
proxies.https (str): The proxy endpoint to use for HTTPS requests.
47+
http_config (dict): A dictionary containing values that control the timeout, proxies, and etc of HTTP requests.
48+
scope (str): The "scope" to use when fetching the bearer token from the IAM token server.
49+
This can be used to obtain an access token with a specific scope.
50+
51+
Keyword Args:
52+
container_token_filename: The name of the file containing the injected CR token value
53+
(applies to IKS-managed compute resources). Defaults to "/var/run/secrets/tokens/vault-token".
54+
iam_profile_name: The name of the linked trusted IAM profile to be used when obtaining the IAM access token
55+
(a CR token might map to multiple IAM profiles). One of IAMProfileName or IAMProfileID must be specified.
56+
sDefaults to None.
57+
iam_profile_id: The id of the linked trusted IAM profile to be used when obtaining the IAM access token
58+
(a CR token might map to multiple IAM profiles). One of IAMProfileName or IAMProfileID must be specified.
59+
Defaults to None.
60+
url: The IAM endpoint to token requests. Defaults to None.
61+
client_id: The client_id and client_secret fields are used to form
62+
a "basic auth" Authorization header for interactions with the IAM token server.
63+
Defaults to None.
64+
client_secret: The client_id and client_secret fields are used to form
65+
a "basic auth" Authorization header for interactions with the IAM token server.
66+
Defaults to None.
67+
disable_ssl_verification: A flag that indicates whether verification of
68+
the server's SSL certificate should be disabled or not. Defaults to False.
69+
headers: Default headers to be sent with every IAM token request. Defaults to None.
70+
proxies: Proxies to use for communicating with IAM. Defaults to None.
71+
proxies.http: The proxy endpoint to use for HTTP requests.
72+
proxies.https: The proxy endpoint to use for HTTPS requests.
73+
scope: The "scope" to use when fetching the bearer token from the IAM token server.
74+
This can be used to obtain an access token with a specific scope.
75+
"""
76+
DEFAULT_CR_TOKEN_FILENAME = '/var/run/secrets/tokens/vault-token'
77+
78+
def __init__(self,
79+
cr_token_filename: Optional[str] = None,
80+
iam_profile_name: Optional[str] = None,
81+
iam_profile_id: Optional[str] = None,
82+
url: Optional[str] = None,
83+
client_id: Optional[str] = None,
84+
client_secret: Optional[str] = None,
85+
disable_ssl_verification: bool = False,
86+
scope: Optional[str] = None,
87+
proxies: Optional[Dict[str, str]] = None,
88+
headers: Optional[Dict[str, str]] = None) -> None:
89+
super().__init__(
90+
url=url, client_id=client_id, client_secret=client_secret,
91+
disable_ssl_verification=disable_ssl_verification, headers=headers, proxies=proxies, scope=scope)
92+
93+
self.cr_token_filename = cr_token_filename
94+
self.iam_profile_name = iam_profile_name
95+
self.iam_profile_id = iam_profile_id
96+
97+
self.request_payload['grant_type'] = 'urn:ibm:params:oauth:grant-type:cr-token'
98+
99+
def retrieve_cr_token(self) -> str:
100+
"""Retrieves the CR token for the current compute resource by reading it from the local file system.
101+
102+
Raises:
103+
Exception: Cannot retrieve the compute resource token from.
104+
105+
Returns:
106+
A string which contains the compute resource token.
107+
"""
108+
cr_token_filename = self.cr_token_filename if self.cr_token_filename else self.DEFAULT_CR_TOKEN_FILENAME
109+
110+
logging.debug('Attempting to read CR token from file: %s', cr_token_filename)
111+
112+
try:
113+
with open(cr_token_filename, 'r') as file:
114+
cr_token = file.read()
115+
return cr_token
116+
# pylint: disable=broad-except
117+
except Exception as ex:
118+
raise Exception('Unable to retrieve the CR token value from file {}: {}'.format(cr_token_filename, ex))
119+
120+
def request_token(self) -> dict:
121+
"""Retrieves a CR token value from the current compute resource,
122+
then uses that to obtain a new IAM access token from the IAM token server.
123+
124+
Returns:
125+
A dictionary containing the bearer token to be subsequently used service requests.
126+
"""
127+
# Retrieve the CR token for this compute resource.
128+
cr_token = self.retrieve_cr_token()
129+
130+
# Set the request payload.
131+
self.request_payload['cr_token'] = cr_token
132+
133+
if self.iam_profile_id:
134+
self.request_payload['profile_id'] = self.iam_profile_id
135+
if self.iam_profile_name:
136+
self.request_payload['profile_name'] = self.iam_profile_name
137+
138+
return super().request_token()
139+
140+
def set_cr_token_filename(self, cr_token_filename: str) -> None:
141+
"""Set the location of the compute resource token on the local filesystem.
142+
143+
Args:
144+
cr_token_filename: path to the compute resource token
145+
"""
146+
self.cr_token_filename = cr_token_filename
147+
148+
def set_iam_profile_name(self, iam_profile_name: str) -> None:
149+
"""Set the name of the IAM profile.
150+
151+
Args:
152+
iam_profile_name: name of the linked trusted IAM profile to be used when obtaining the IAM access token
153+
"""
154+
self.iam_profile_name = iam_profile_name
155+
156+
def set_iam_profile_id(self, iam_profile_id: str) -> None:
157+
"""Set the id of the IAM profile.
158+
159+
Args:
160+
iam_profile_id: id of the linked trusted IAM profile to be used when obtaining the IAM access token
161+
"""
162+
self.iam_profile_id = iam_profile_id

0 commit comments

Comments
 (0)