From a2a961feb8a92941b2c193551a725b41096594ed Mon Sep 17 00:00:00 2001 From: nanhe Date: Tue, 31 Jan 2023 15:08:43 +0800 Subject: [PATCH] support oidc credential in credential chain && support ALIBABA_CLOUD_ROLE_ARN/ALIBABA_CLOUD_OIDC_PROVIDER_ARN/ALIBABA_CLOUD_ROLE_SESSION_NAME --- .github/workflows/testPython.yml | 9 ++++- alibabacloud_credentials/providers.py | 35 ++++++++----------- alibabacloud_credentials/utils/auth_util.py | 7 ++++ tests/test_client.py | 4 +++ tests/test_integration.py | 37 +++++++++++++++++++++ tests/test_providers.py | 24 +++++++++++-- 6 files changed, 92 insertions(+), 24 deletions(-) create mode 100644 tests/test_integration.py diff --git a/.github/workflows/testPython.yml b/.github/workflows/testPython.yml index 9a794e8..518175a 100644 --- a/.github/workflows/testPython.yml +++ b/.github/workflows/testPython.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [ 3.6, 3.7, 3.8, 3.9 ] + python-version: [ 3.7, 3.8, 3.9 ] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -23,5 +23,12 @@ jobs: - name: Test with unittest run: | coverage run --source="./alibabcloud_credentials" -m pytest tests/ + env: + SUB_ALIBABA_CLOUD_ACCESS_KEY: ${{ secrets.SUB_ALIBABA_CLOUD_ACCESS_KEY }} + SUB_ALIBABA_CLOUD_SECRET_KEY: ${{ secrets.SUB_ALIBABA_CLOUD_SECRET_KEY }} + ALIBABA_CLOUD_ROLE_ARN: ${{ secrets.ALIBABA_CLOUD_ROLE_ARN }} + ALIBABA_CLOUD_ROLE_SESSION_NAME: ${{ secrets.ALIBABA_CLOUD_ROLE_SESSION_NAME }} + ALIBABA_CLOUD_OIDC_TOKEN_FILE: ${{ secrets.ALIBABA_CLOUD_OIDC_TOKEN_FILE }} + ALIBABA_CLOUD_OIDC_PROVIDER_ARN: ${{ secrets.ALIBABA_CLOUD_OIDC_PROVIDER_ARN }} - name: CodeCov run: bash <(curl -s https://codecov.io/bash) -cF python \ No newline at end of file diff --git a/alibabacloud_credentials/providers.py b/alibabacloud_credentials/providers.py index 18b8236..33b6521 100644 --- a/alibabacloud_credentials/providers.py +++ b/alibabacloud_credentials/providers.py @@ -63,10 +63,19 @@ class DefaultCredentialsProvider(AlibabaCloudCredentialsProvider): def __init__(self): super().__init__() self.user_configuration_providers = [ - EnvironmentVariableCredentialsProvider(), - ProfileCredentialsProvider() + EnvironmentVariableCredentialsProvider() ] + if au.enable_oidc_credential: + self.user_configuration_providers.append(OIDCRoleArnCredentialProvider( + role_session_name=au.environment_role_session_name, + role_arn=au.environment_role_arn, + oidc_provider_arn=au.environment_oidc_provider_arn, + oidc_token_file_path=au.environment_oidc_token_file + )) + + self.user_configuration_providers.append(ProfileCredentialsProvider()) role_name = au.environment_ECSMeta_data + if role_name is not None: self.user_configuration_providers.append(EcsRamRoleCredentialProvider(role_name)) self.user_configuration_providers.append(CredentialsUriProvider()) @@ -294,7 +303,7 @@ def __init__(self, access_key_id=None, access_key_secret=None, role_session_name oidc_token_file_path=None, region_id=None, policy=None, config=None): - self._verify_empty_args(access_key_id, access_key_secret, config=config) + self._verify_empty_args(role_arn, oidc_provider_arn, oidc_token_file_path, config=config) super().__init__(config) self._set_arg('role_arn', role_arn) self._set_arg('oidc_provider_arn', oidc_provider_arn) @@ -307,8 +316,6 @@ def __init__(self, access_key_id=None, access_key_secret=None, role_session_name else: raise CredentialException( 'The oidc_token_file_path does not exist and env ALIBABA_CLOUD_OIDC_TOKEN_FILE is none.') - self._set_arg('access_key_id', access_key_id) - self._set_arg('access_key_secret', access_key_secret) self._set_arg('region_id', region_id) self._set_arg('role_session_name', role_session_name) self._set_arg('policy', policy) @@ -329,19 +336,12 @@ def _create_credentials(self, turl=None): 'RoleArn': self.role_arn, 'OIDCProviderArn': self.oidc_provider_arn, 'OIDCToken': oidc_token, - 'RoleSessionName': self.role_session_name, - 'SignatureMethod': 'HMAC-SHA1', - 'SignatureVersion': '1.0' + 'RoleSessionName': self.role_session_name if self.role_session_name else 'defaultSessionName' } tea_request.query["Timestamp"] = ph.get_iso_8061_date() tea_request.query["SignatureNonce"] = ph.get_uuid() if self.policy is not None: tea_request.query["Policy"] = self.policy - string_to_sign = ph.compose_string_to_sign("GET", tea_request.query) - if self.access_key_id is not None and self.access_key_secret is not None: - tea_request.query["AccessKeyId"] = self.access_key_id - signature = ph.sign_string(string_to_sign, self.access_key_secret + "&") - tea_request.query["Signature"] = signature tea_request.protocol = 'https' tea_request.headers['host'] = turl if turl else 'sts.aliyuncs.com' # request @@ -374,19 +374,12 @@ async def _create_credentials_async(self, turl=None): 'RoleArn': self.role_arn, 'OIDCProviderArn': self.oidc_provider_arn, 'OIDCToken': oidc_token, - 'RoleSessionName': self.role_session_name, - 'SignatureMethod': 'HMAC-SHA1', - 'SignatureVersion': '1.0' + 'RoleSessionName': self.role_session_name if self.role_session_name else 'defaultSessionName' } tea_request.query["Timestamp"] = ph.get_iso_8061_date() tea_request.query["SignatureNonce"] = ph.get_uuid() if self.policy is not None: tea_request.query["Policy"] = self.policy - string_to_sign = ph.compose_string_to_sign("GET", tea_request.query) - if self.access_key_id is not None and self.access_key_secret is not None: - tea_request.query["AccessKeyId"] = self.access_key_id - signature = ph.sign_string(string_to_sign, self.access_key_secret + "&") - tea_request.query["Signature"] = signature tea_request.protocol = 'https' tea_request.headers['host'] = turl if turl else 'sts.aliyuncs.com' # request diff --git a/alibabacloud_credentials/utils/auth_util.py b/alibabacloud_credentials/utils/auth_util.py index e3bc6d5..ad65b72 100644 --- a/alibabacloud_credentials/utils/auth_util.py +++ b/alibabacloud_credentials/utils/auth_util.py @@ -6,6 +6,13 @@ environment_ECSMeta_data = os.environ.get('ALIBABA_CLOUD_ECS_METADATA') environment_credentials_file = os.environ.get('ALIBABA_CLOUD_CREDENTIALS_FILE') environment_oidc_token_file = os.environ.get('ALIBABA_CLOUD_OIDC_TOKEN_FILE') +environment_role_arn = os.environ.get('ALIBABA_CLOUD_ROLE_ARN') +environment_oidc_provider_arn = os.environ.get('ALIBABA_CLOUD_OIDC_PROVIDER_ARN') +environment_role_session_name = os.environ.get('ALIBABA_CLOUD_ROLE_SESSION_NAME') + +enable_oidc_credential = environment_oidc_token_file is not None \ + and environment_role_arn is not None \ + and environment_oidc_provider_arn is not None private_key = None diff --git a/tests/test_client.py b/tests/test_client.py index 39ab909..34630da 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -5,6 +5,7 @@ from alibabacloud_credentials.utils import auth_constant from alibabacloud_credentials.client import Client from alibabacloud_credentials import credentials +from alibabacloud_credentials.utils import auth_util class TestClient(unittest.TestCase): @@ -18,11 +19,14 @@ def test_client_ak(self): self.assertEqual('654321', cred.get_access_key_secret()) self.assertEqual(auth_constant.ACCESS_KEY, cred.get_type()) self.assertIsNone(cred.get_security_token()) + enable_oidc_credential = auth_util.enable_oidc_credential + auth_util.enable_oidc_credential = False try: cred = Client() cred.get_access_key_id() except Exception as ex: self.assertEqual('not found credentials', str(ex)) + auth_util.enable_oidc_credential = enable_oidc_credential def test_client_sts(self): conf = Config(type='sts') diff --git a/tests/test_integration.py b/tests/test_integration.py new file mode 100644 index 0000000..4e5db15 --- /dev/null +++ b/tests/test_integration.py @@ -0,0 +1,37 @@ +import os +import unittest + +from alibabacloud_credentials import providers, models +from alibabacloud_credentials.client import Client +from alibabacloud_credentials.exceptions import CredentialException +from alibabacloud_credentials.utils import auth_util + + +class TestIntegration(unittest.TestCase): + def test_RamRoleArn(self): + access_key_id = os.environ.get('SUB_ALIBABA_CLOUD_ACCESS_KEY') + access_key_secret = os.environ.get('SUB_ALIBABA_CLOUD_SECRET_KEY') + role_session_name = os.environ.get('ALIBABA_CLOUD_ROLE_SESSION_NAME') + role_arn = os.environ.get('ALIBABA_CLOUD_ROLE_ARN') + + conf = models.Config( + access_key_id=access_key_id, + access_key_secret=access_key_secret, + role_session_name=role_session_name, + role_arn=role_arn + ) + prov = providers.RamRoleArnCredentialProvider(config=conf) + cred = prov.get_credentials() + self.assertIsNotNone(cred.access_key_id) + + def test_OIDCRoleArn(self): + self.assertIsNotNone(auth_util.environment_role_arn) + self.assertIsNotNone(auth_util.environment_oidc_provider_arn) + self.assertIsNotNone(auth_util.environment_role_session_name) + self.assertIsNotNone(auth_util.environment_oidc_token_file) + self.assertTrue(auth_util.enable_oidc_credential) + try: + default_client = Client() + default_client.get_access_key_id() + except CredentialException as e: + self.assertRegex(e.message, 'AuthenticationFail.OIDCToken.Invalid') diff --git a/tests/test_providers.py b/tests/test_providers.py index 6db1d38..ddf6f63 100644 --- a/tests/test_providers.py +++ b/tests/test_providers.py @@ -113,6 +113,25 @@ def test_DefaultCredentialsProvider(self): prov.clear_credentials_provider() self.assertRaises(exceptions.CredentialException, prov.get_credentials) + environment_role_arn = auth_util.environment_role_arn + environment_oidc_provider_arn = auth_util.environment_oidc_provider_arn + environment_oidc_token_file = auth_util.environment_oidc_token_file + enable_oidc_credential = auth_util.enable_oidc_credential + + auth_util.environment_role_arn = 'acs:ram::roleArn:role/roleArn' + auth_util.environment_oidc_provider_arn = 'acs:ram::roleArn' + auth_util.environment_oidc_token_file = 'tests/private_key.txt' + auth_util.enable_oidc_credential = True + prov = providers.DefaultCredentialsProvider() + try: + prov.get_credentials() + except Exception as e: + self.assertRegex(e.message, 'AuthenticationFail.OIDCToken.Invalid') + auth_util.environment_role_arn = environment_role_arn + auth_util.environment_oidc_provider_arn = environment_oidc_provider_arn + auth_util.environment_oidc_token_file = environment_oidc_token_file + auth_util.enable_oidc_credential = enable_oidc_credential + def test_RamRoleArnCredentialProvider(self): access_key_id, access_key_secret, role_session_name, role_arn, region_id, policy = \ 'access_key_id', 'access_key_secret', 'role_session_name', 'role_arn', 'region_id', 'policy' @@ -149,8 +168,6 @@ def test_OIDCRoleArnCredentialProvider(self): prov = providers.OIDCRoleArnCredentialProvider( access_key_id, access_key_secret, role_session_name, role_arn, oidc_provider_arn, oidc_token_file_path, region_id, policy ) - self.assertEqual('access_key_id', prov.access_key_id) - self.assertEqual('access_key_secret', prov.access_key_secret) self.assertEqual('role_session_name', prov.role_session_name) self.assertEqual('role_arn', prov.role_arn) self.assertEqual('oidc_provider_arn', prov.oidc_provider_arn) @@ -304,3 +321,6 @@ def test_EnvironmentVariableCredentialsProvider(self): auth_util.environment_access_key_id = 'a' auth_util.environment_access_key_secret = '' self.assertRaises(exceptions.CredentialException, prov.get_credentials) + + auth_util.environment_access_key_id = None + auth_util.environment_access_key_secret = None