Skip to content

Commit 5ee10ad

Browse files
committed
feat(auth): add support for MCSP V2 authentication
This commit adds the MCSPV2Authenticator implementation. This authenticator will invoke the MCSP V2 POST /api/2.0/{scopeCollectionType}/{scopeId}/apikeys/token operation to obtain an access token for an apikey. Signed-off-by: Phil Adams <[email protected]>
1 parent 989d800 commit 5ee10ad

17 files changed

+1595
-384
lines changed

.github/workflows/build.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# semantic-release is also run to create a new release (if
44
# warranted by the new commits being built).
55

6-
name: Build/Test
6+
name: build
77

88
on:
99
push:
@@ -16,7 +16,7 @@ on:
1616
jobs:
1717
detect-secrets:
1818
if: "!contains(github.event.head_commit.message, '[skip ci]')"
19-
name: Detect-Secrets
19+
name: detect-secrets
2020
runs-on: ubuntu-latest
2121

2222
steps:
@@ -38,8 +38,8 @@ jobs:
3838
detect-secrets -v audit --report --fail-on-unaudited --fail-on-live --fail-on-audited-real .secrets.baseline
3939
4040
build:
41+
name: build-test (python ${{ matrix.python-version }})
4142
needs: detect-secrets
42-
name: Build/Test (Python ${{ matrix.python-version }})
4343

4444
runs-on: ubuntu-latest
4545
strategy:
@@ -59,8 +59,8 @@ jobs:
5959
run: make ci
6060

6161
create-release:
62+
name: semantic-release
6263
needs: build
63-
name: Semantic-Release
6464
if: "github.ref_name == 'main' && github.event_name != 'pull_request'"
6565
runs-on: ubuntu-latest
6666

.github/workflows/publish.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# - building and publishing javadocs to the git repository.
44
# It is triggered when a new release is created.
55

6-
name: Publish
6+
name: publish
77
on:
88
release:
99
types: [created]
@@ -12,7 +12,7 @@ on:
1212

1313
jobs:
1414
publish:
15-
name: Publish Release
15+
name: publish-pypi
1616
runs-on: ubuntu-latest
1717

1818
steps:

.pylintrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ disable=
88
too-many-arguments,
99
unnecessary-pass,
1010
no-member,
11-
consider-using-f-string
11+
consider-using-f-string,
12+
too-many-instance-attributes
1213

1314
[TYPECHECK]
1415
ignored-classes= responses

.secrets.baseline

Lines changed: 59 additions & 23 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": "2025-01-09T21:56:01Z",
6+
"generated_at": "2025-05-27T14:04:58Z",
77
"plugins_used": [
88
{
99
"name": "AWSKeyDetector"
@@ -70,55 +70,55 @@
7070
"hashed_secret": "91dfd9ddb4198affc5c194cd8ce6d338fde470e2",
7171
"is_secret": false,
7272
"is_verified": false,
73-
"line_number": 66,
73+
"line_number": 67,
7474
"type": "Secret Keyword",
7575
"verified_result": null
7676
},
7777
{
7878
"hashed_secret": "4f51cde3ac0a5504afa4bc06859b098366592c19",
7979
"is_secret": false,
8080
"is_verified": false,
81-
"line_number": 207,
81+
"line_number": 208,
8282
"type": "Secret Keyword",
8383
"verified_result": null
8484
},
8585
{
8686
"hashed_secret": "e87559ed7decb62d0733ae251ae58d42a55291d8",
8787
"is_secret": false,
8888
"is_verified": false,
89-
"line_number": 209,
89+
"line_number": 210,
9090
"type": "Secret Keyword",
9191
"verified_result": null
9292
},
9393
{
9494
"hashed_secret": "12f4a68ed3d0863e56497c9cdb1e2e4e91d5cb68",
9595
"is_secret": false,
9696
"is_verified": false,
97-
"line_number": 273,
97+
"line_number": 274,
9898
"type": "Secret Keyword",
9999
"verified_result": null
100100
},
101101
{
102102
"hashed_secret": "c837b75d7cd93ef9c2243ca28d6e5156259fd253",
103103
"is_secret": false,
104104
"is_verified": false,
105-
"line_number": 277,
105+
"line_number": 278,
106106
"type": "Secret Keyword",
107107
"verified_result": null
108108
},
109109
{
110110
"hashed_secret": "98635b2eaa2379f28cd6d72a38299f286b81b459",
111111
"is_secret": false,
112112
"is_verified": false,
113-
"line_number": 502,
113+
"line_number": 505,
114114
"type": "Secret Keyword",
115115
"verified_result": null
116116
},
117117
{
118118
"hashed_secret": "47fcf185ee7e15fe05cae31fbe9e4ebe4a06a40d",
119119
"is_secret": false,
120120
"is_verified": false,
121-
"line_number": 597,
121+
"line_number": 694,
122122
"type": "Secret Keyword",
123123
"verified_result": null
124124
}
@@ -213,6 +213,16 @@
213213
"verified_result": null
214214
}
215215
],
216+
"resources/ibm-credentials-mcspv2.env": [
217+
{
218+
"hashed_secret": "f2e7745f43b0ef0e2c2faf61d6c6a28be2965750",
219+
"is_secret": false,
220+
"is_verified": false,
221+
"line_number": 23,
222+
"type": "Secret Keyword",
223+
"verified_result": null
224+
}
225+
],
216226
"resources/ibm-credentials-retry.env": [
217227
{
218228
"hashed_secret": "ce49dd46e23153d6593eccd68534b9f1465d5bbd",
@@ -341,6 +351,40 @@
341351
"verified_result": null
342352
}
343353
],
354+
"test/test_get_authenticator.py": [
355+
{
356+
"hashed_secret": "34a0a47a51d5bf739df0214450385e29ee7e9847",
357+
"is_secret": false,
358+
"is_verified": false,
359+
"line_number": 256,
360+
"type": "Secret Keyword",
361+
"verified_result": null
362+
},
363+
{
364+
"hashed_secret": "f2e7745f43b0ef0e2c2faf61d6c6a28be2965750",
365+
"is_secret": false,
366+
"is_verified": false,
367+
"line_number": 267,
368+
"type": "Secret Keyword",
369+
"verified_result": null
370+
},
371+
{
372+
"hashed_secret": "2863fa4b5510c46afc2bd2998dfbc0cf3d6df032",
373+
"is_secret": false,
374+
"is_verified": false,
375+
"line_number": 348,
376+
"type": "Secret Keyword",
377+
"verified_result": null
378+
},
379+
{
380+
"hashed_secret": "b9cad336062c0dc3bb30145b1a6697fccfe755a6",
381+
"is_secret": false,
382+
"is_verified": false,
383+
"line_number": 409,
384+
"type": "Secret Keyword",
385+
"verified_result": null
386+
}
387+
],
344388
"test/test_iam_assume_authenticator.py": [
345389
{
346390
"hashed_secret": "4080eeeaf54faf879b9e8d99c49a8503f7e855bb",
@@ -507,36 +551,28 @@
507551
"verified_result": null
508552
}
509553
],
510-
"test/test_utils.py": [
511-
{
512-
"hashed_secret": "34a0a47a51d5bf739df0214450385e29ee7e9847",
513-
"is_secret": false,
514-
"is_verified": false,
515-
"line_number": 453,
516-
"type": "Secret Keyword",
517-
"verified_result": null
518-
},
554+
"test/test_mcspv2_authenticator.py": [
519555
{
520556
"hashed_secret": "f2e7745f43b0ef0e2c2faf61d6c6a28be2965750",
521557
"is_secret": false,
522558
"is_verified": false,
523-
"line_number": 464,
559+
"line_number": 15,
524560
"type": "Secret Keyword",
525561
"verified_result": null
526562
},
527563
{
528-
"hashed_secret": "2863fa4b5510c46afc2bd2998dfbc0cf3d6df032",
564+
"hashed_secret": "da2f27d2c57a0e1ed2dc3a34b4ef02faf2f7a4c2",
529565
"is_secret": false,
530566
"is_verified": false,
531-
"line_number": 545,
532-
"type": "Secret Keyword",
567+
"line_number": 246,
568+
"type": "Hex High Entropy String",
533569
"verified_result": null
534570
},
535571
{
536-
"hashed_secret": "b9cad336062c0dc3bb30145b1a6697fccfe755a6",
572+
"hashed_secret": "ace1a5bf229c3af3f699369c6f2fa1a628692ab8",
537573
"is_secret": false,
538574
"is_verified": false,
539-
"line_number": 606,
575+
"line_number": 392,
540576
"type": "Secret Keyword",
541577
"verified_result": null
542578
}

Authentication.md

Lines changed: 103 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ The python-sdk-core project supports the following types of authentication:
77
- Container Authentication
88
- VPC Instance Authentication
99
- Cloud Pak for Data Authentication
10-
- Multi-Cloud Saas Platform (MCSP) Authentication
10+
- Multi-Cloud Saas Platform (MCSP) V1 Authentication
11+
- Multi-Cloud Saas Platform (MCSP) V2 Authentication
1112
- No Authentication (for testing)
1213

1314
The SDK user configures the appropriate type of authentication for use with service instances.
@@ -546,11 +547,11 @@ service = ExampleServiceV1.new_instance(service_name='example_service')
546547
```
547548

548549

549-
## Multi-Cloud Saas Platform (MCSP) Authentication
550+
## Multi-Cloud Saas Platform (MCSP) V1 Authentication
550551
The `MCSPAuthenticator` can be used in scenarios where an application needs to
551552
interact with an IBM Cloud service that has been deployed to a non-IBM Cloud environment (e.g. AWS).
552-
It accepts a user-supplied apikey and performs the necessary interactions with the
553-
Multi-Cloud Saas Platform token service to obtain a suitable MCSP access token (a bearer token)
553+
It accepts a user-supplied apikey and invokes the Multi-Cloud Saas Platform token service's
554+
`POST /siusermgr/api/1.0/apikeys/token` operation to obtain a suitable MCSP access token (a bearer token)
554555
for the specified apikey.
555556
The authenticator will also obtain a new bearer token when the current token expires.
556557
The bearer token is then added to each outbound request in the `Authorization` header in the
@@ -610,6 +611,104 @@ service = ExampleServiceV1.new_instance(service_name='example_service')
610611
```
611612

612613

614+
## Multi-Cloud Saas Platform (MCSP) V2 Authentication
615+
The `MCSPV2Authenticator` can be used in scenarios where an application needs to
616+
interact with an IBM Cloud service that has been deployed to a non-IBM Cloud environment (e.g. AWS).
617+
It accepts a user-supplied apikey and invokes the Multi-Cloud Saas Platform token service's
618+
`POST /api/2.0/{scopeCollectionType}/{scopeId}/apikeys/token` operation to obtain a suitable MCSP access token (a bearer token)
619+
for the specified apikey.
620+
The authenticator will also obtain a new bearer token when the current token expires.
621+
The bearer token is then added to each outbound request in the `Authorization` header in the
622+
form:
623+
```
624+
Authorization: Bearer <bearer-token>
625+
```
626+
627+
### Properties
628+
629+
- apikey: (required) The apikey to be used to obtain an MCSP access token.
630+
631+
- url: (required) The URL representing the MCSP token service endpoint's base URL string. Do not include the
632+
operation path (e.g. `/api/2.0/{scopeCollectionType}/{scopeId}/apikeys/token`) as part of this property's value.
633+
634+
- scope_collection_type: (required) The scope collection type of item(s).
635+
The valid values are: `accounts`, `subscriptions`, `services`.
636+
637+
- scope_id: (required) The scope identifier of item(s).
638+
639+
- include_builtin_actions: (optional) A flag to include builtin actions in the `actions` claim in the MCSP token (default: false).
640+
641+
- include_custom_actions: (optional) A flag to include custom actions in the `actions` claim in the MCSP token (default: false).
642+
643+
- include_roles: (optional) A flag to include the `roles` claim in the MCSP token (default: true).
644+
645+
- prefix_roles: (optional) A flag to add a prefix with the scope level where
646+
the role is defined in the `roles` claim (default: false).
647+
648+
- caller_ext_claim: (optional) A map containing keys and values to be injected into the returned access token
649+
as the `callerExt` claim. The keys used in this map must be enabled in the apikey by setting the
650+
`callerExtClaimNames` property when the apikey is created.
651+
This property is typically only used in scenarios involving an apikey with identityType `SERVICEID`.
652+
653+
- disable_ssl_verification: (optional) A flag that indicates whether verification of the server's SSL
654+
certificate should be disabled or not. The default value is `false`.
655+
656+
- headers: (optional) A set of key/value pairs that will be sent as HTTP headers in requests
657+
made to the MCSP token service.
658+
659+
### Usage Notes
660+
- When constructing an MCSPV2Authenticator instance, the apikey, url, scope_collection_type, and scope_id properties are required.
661+
662+
- If you specify the caller_ext_claim map, the keys used in the map must have been previously enabled in the apikey
663+
by setting the `callerExtClaimNames` property when you created the apikey.
664+
The entries contained in this map will appear in the `callerExt` field (claim) of the returned access token.
665+
666+
- The authenticator will invoke the token server's `POST /api/2.0/{scopeCollectionType}/{scopeId}/apikeys/token` operation to
667+
exchange the apikey for an MCSP access token (the bearer token).
668+
669+
### Programming example
670+
```python
671+
from ibm_cloud_sdk_core.authenticators import MCSPV2Authenticator
672+
from <sdk-package-name>.example_service_v1 import *
673+
674+
# Create the authenticator.
675+
authenticator = MCSPV2Authenticator(
676+
apikey='myapikey',
677+
url='https://example.mcspv2.token-exchange.com',
678+
scope_collection_type='accounts',
679+
scope_id='20250519-2128-3755-60b3-103e01c509e8',
680+
include_builtin_actions=True,
681+
caller_ext_claim={'productID': 'prod-123'},
682+
)
683+
684+
# Construct the service instance.
685+
service = ExampleServiceV1(authenticator=authenticator)
686+
687+
# 'service' can now be used to invoke operations.
688+
```
689+
690+
### Configuration example
691+
External configuration:
692+
```
693+
export EXAMPLE_SERVICE_AUTH_TYPE=mcspv2
694+
export EXAMPLE_SERVICE_APIKEY=myapikey
695+
export EXAMPLE_SERVICE_AUTH_URL=https://example.mcspv2.token-exchange.com
696+
export EXAMPLE_SERVICE_SCOPE_COLLECTION_TYPE=accounts
697+
export EXAMPLE_SERVICE_SCOPE_ID=20250519-2128-3755-60b3-103e01c509e8
698+
export EXAMPLE_SERVICE_INCLUDE_BUILTIN_ACTIONS=true
699+
export EXAMPLE_SERVICE_CALLER_EXT_CLAIM={"productID":"prod-123"}
700+
```
701+
Application code:
702+
```python
703+
from <sdk-package-name>.example_service_v1 import *
704+
705+
# Construct the service instance.
706+
service = ExampleServiceV1.new_instance(service_name='example_service')
707+
708+
# 'service' can now be used to invoke operations.
709+
```
710+
711+
613712
## No Auth Authentication
614713
The `NoAuthAuthenticator` is a placeholder authenticator which performs no actual authentication function.
615714
It can be used in situations where authentication needs to be bypassed, perhaps while developing

ibm_cloud_sdk_core/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
from .token_managers.container_token_manager import ContainerTokenManager
4747
from .token_managers.vpc_instance_token_manager import VPCInstanceTokenManager
4848
from .token_managers.mcsp_token_manager import MCSPTokenManager
49+
from .token_managers.mcspv2_token_manager import MCSPV2TokenManager
4950
from .api_exception import ApiException
5051
from .utils import datetime_to_string, string_to_datetime, read_external_sources
5152
from .utils import datetime_to_string_list, string_to_datetime_list

ibm_cloud_sdk_core/authenticators/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,14 @@
2828
Authenticator: Abstract Base Class. Implement this interface to provide custom authentication schemes to services.
2929
BasicAuthenticator: Authenticator for passing supplied basic authentication information to service endpoint.
3030
BearerTokenAuthenticator: Authenticator for passing supplied bearer token to service endpoint.
31+
ContainerAuthenticator: Authenticator for use in a container environment.
3132
CloudPakForDataAuthenticator: Authenticator for passing CP4D authentication information to service endpoint.
3233
IAMAuthenticator: Authenticator for passing IAM authentication information to service endpoint.
34+
IAMAssumeAuthenticator: Authenticator for the "assume" grant type.
35+
VPCInstanceAuthenticator: Authenticator for use within a VPC instance.
3336
NoAuthAuthenticator: Performs no authentication. Useful for testing purposes.
37+
MCSPAuthenticator: Authenticator that supports the MCSP v1 token exchange.
38+
MCSPV2Authenticator: Authenticator that supports the MCSP v2 token exchange.
3439
"""
3540

3641
from .authenticator import Authenticator
@@ -43,3 +48,4 @@
4348
from .vpc_instance_authenticator import VPCInstanceAuthenticator
4449
from .no_auth_authenticator import NoAuthAuthenticator
4550
from .mcsp_authenticator import MCSPAuthenticator
51+
from .mcspv2_authenticator import MCSPV2Authenticator

0 commit comments

Comments
 (0)