From 40aafcd08551e58b435d3808f50c2818e0841789 Mon Sep 17 00:00:00 2001 From: "Mark J. Reed" Date: Thu, 5 Sep 2024 14:28:52 -0400 Subject: [PATCH] feat: support arbitrary delimiters in acc-role profile names --- README.md | 2 +- gimme_aws_creds/config.py | 7 +++---- gimme_aws_creds/main.py | 21 ++++----------------- gimme_aws_creds/profiles.py | 34 ++++++++++++++++++++++++++++++++++ tests/test_main.py | 27 +++++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 22 deletions(-) create mode 100644 gimme_aws_creds/profiles.py diff --git a/README.md b/README.md index 756efb0..5aab101 100644 --- a/README.md +++ b/README.md @@ -213,7 +213,7 @@ A configuration wizard will prompt you to enter the necessary configuration para - cred_profile - If writing to the AWS cred file, this sets the name of the AWS credential profile. - The reserved word `role` will use the name component of the role arn as the profile name. i.e. arn:aws:iam::123456789012:role/okta-1234-role becomes section [okta-1234-role] in the aws credentials file - The reserved word `acc` will use the account number (or alias if `resolve_aws_alias` is set to y) as the profile name. i.e. arn:aws:iam::123456789012:role/okta-1234-role becomes section [arn:aws:iam::123456789012] or if `resolve_aws_alias` [okta-1234-role] in the aws credentials file. - - The reserved word `acc-role` will use the name component of the role arn prepended with account number (or alias if `resolve_aws_alias` is set to y) to avoid collisions, i.e. arn:aws:iam::123456789012:role/okta-1234-role becomes section [123456789012-okta-1234-role], or if `resolve_aws_alias` [okta-1234-role] in the aws credentials file + - The reserved word `acc-role` will use the name component of the role arn prepended with account number (or alias if `resolve_aws_alias` is set to y) to avoid collisions, i.e. arn:aws:iam::123456789012:role/okta-1234-role becomes section [123456789012-okta-1234-role], or if `resolve_aws_alias` [okta-1234-role] in the aws credentials file. The hyphen may be replaced by any character. - If set to `default` then the temp creds will be stored in the default profile - Note: if there are multiple roles, and `default` is selected it will be overwritten multiple times and last role wins. The same happens when `role` is selected and you have many accounts with the same role names. Consider using `acc-role` if this happens. - aws_appname - This is optional. The Okta AWS App name, which has the role you want to assume. diff --git a/gimme_aws_creds/config.py b/gimme_aws_creds/config.py index b5a881b..b53af86 100644 --- a/gimme_aws_creds/config.py +++ b/gimme_aws_creds/config.py @@ -15,7 +15,7 @@ import requests from urllib.parse import urlparse -from . import errors, ui, version +from . import errors, profiles, ui, version class Config(object): @@ -513,7 +513,7 @@ def _get_cred_profile(self, default_entry): "The AWS credential profile defines which profile is used to store the temp AWS creds.\n" "If set to 'role' then a new profile will be created matching the role name assumed by the user.\n" "If set to 'acc' then a new profile will be created matching the account number.\n" - "If set to 'acc-role' then a new profile will be created matching the role name assumed by the user, but prefixed with account number to avoid collisions.\n" + "If set to 'acc-role' then a new profile will be created matching the role name assumed by the user, but prefixed with account number (or alias if resolve_alias is true) to avoid collisions. Any character may be substituted for the hyphen.\n" "If set to 'default' then the temp creds will be stored in the default profile\n" "If set to any other value, the name of the profile will match that value." ) @@ -521,8 +521,7 @@ def _get_cred_profile(self, default_entry): cred_profile = self._get_user_input( "AWS Credential Profile", default_entry) - if cred_profile.lower() in ['default', 'role', 'acc', 'acc-role']: - cred_profile = cred_profile.lower() + cred_profile = profiles.Profile(cred_profile).canonicalize() return cred_profile diff --git a/gimme_aws_creds/main.py b/gimme_aws_creds/main.py index b110602..ccb9e61 100644 --- a/gimme_aws_creds/main.py +++ b/gimme_aws_creds/main.py @@ -35,6 +35,7 @@ from .okta_identity_engine import OktaIdentityEngine from .okta_classic import OktaClassicClient from .registered_authenticators import RegisteredAuthenticators +from .profiles import Profile class GimmeAWSCreds(object): @@ -809,23 +810,9 @@ def prepare_data(self, role, generate_credentials=False): } def get_profile_name(self, cred_profile, include_path, naming_data, resolve_alias, role): - if cred_profile.lower() == 'default': - profile_name = 'default' - elif cred_profile.lower() == 'role': - profile_name = naming_data['role'] - elif cred_profile.lower() == 'acc': - profile_name = self._get_account_name(naming_data['account'], role, resolve_alias) - elif cred_profile.lower() == 'acc-role': - account = self._get_account_name(naming_data['account'], role, resolve_alias) - role_name = naming_data['role'] - path = naming_data['path'] - if include_path is True: - role_name = ''.join([path, role_name]) - profile_name = '-'.join([account, - role_name]) - else: - profile_name = cred_profile - return profile_name + cred_profile = Profile(cred_profile, include_path) + account = self._get_account_name(naming_data['account'], role, resolve_alias) + return cred_profile.name_for(account, naming_data['role'], naming_data['path']) def _get_account_name(self, account, role, resolve_alias): if resolve_alias is False: diff --git a/gimme_aws_creds/profiles.py b/gimme_aws_creds/profiles.py new file mode 100644 index 0000000..08b147b --- /dev/null +++ b/gimme_aws_creds/profiles.py @@ -0,0 +1,34 @@ +""" + The Profile class generates a profile name from the cred_profile configuration parameter. +""" +class Profile: + def __init__(self, cred_profile, include_path): + self.cred_profile = cred_profile + self.include_path = include_path + + def canonicalize(self): + lc_profile = self.cred_profile.lower() + if lc_profile in ['default', 'role', 'acc'] or self._is_delimited(lc_profile): + return lc_profile + else: + return self.cred_profile + + def _is_delimited(self, lc_profile): + if len(lc_profile)==8 and lc_profile.startswith('acc') and lc_profile.endswith('role'): + return lc_profile[3] + return False + + def name_for(self, account, role_name, path='/'): + lc_profile = self.cred_profile.lower() + if lc_profile == 'default': + return 'default' + elif lc_profile == 'role': + return role_name + elif lc_profile == 'acc': + return account + elif delimiter := self._is_delimited(lc_profile): + if self.include_path: + role_name = ''.join([path, role_name]) + return delimiter.join([account, role_name]) + else: + return self.cred_profile diff --git a/tests/test_main.py b/tests/test_main.py index f6469b2..293c1b6 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -201,6 +201,19 @@ def test_get_profile_name_accrole_resolve_alias_do_not_include_paths(self): include_path = False self.assertEqual(creds.get_profile_name(cred_profile, include_path, naming_data, resolve_alias, role), "my-org-master-administrator") + def test_get_profile_name_accrole_colon_resolve_alias_do_not_include_paths(self): + "Testing the acc:role, with alias resolution, and not including full role path" + creds = GimmeAWSCreds() + naming_data = {'account': '123456789012', 'role': 'administrator', 'path': '/administrator/'} + role = RoleSet(idp='arn:aws:iam::123456789012:saml-provider/my-okta-provider', + role='arn:aws:iam::123456789012:role/administrator/administrator', + friendly_account_name='Account: my-org-master (123456789012)', + friendly_role_name='administrator/administrator') + cred_profile = 'acc:role' + resolve_alias = True + include_path = False + self.assertEqual(creds.get_profile_name(cred_profile, include_path, naming_data, resolve_alias, role), "my-org-master:administrator") + def test_get_profile_accrole_name_do_not_resolve_alias_do_not_include_paths(self): "Testing the acc-role, without alias resolution, and not including full role path" creds = GimmeAWSCreds() @@ -215,6 +228,20 @@ def test_get_profile_accrole_name_do_not_resolve_alias_do_not_include_paths(self self.assertEqual(creds.get_profile_name(cred_profile, include_path, naming_data, resolve_alias, role), "123456789012-administrator") + def test_get_profile_acc_slash_role_name_do_not_resolve_alias_do_not_include_paths(self): + "Testing the acc/role, without alias resolution, and not including full role path" + creds = GimmeAWSCreds() + naming_data = {'account': '123456789012', 'role': 'administrator', 'path': '/administrator/'} + role = RoleSet(idp='arn:aws:iam::123456789012:saml-provider/my-okta-provider', + role='arn:aws:iam::123456789012:role/administrator/administrator', + friendly_account_name='Account: my-org-master (123456789012)', + friendly_role_name='administrator/administrator') + cred_profile = 'acc/role' + resolve_alias = False + include_path = False + self.assertEqual(creds.get_profile_name(cred_profile, include_path, naming_data, resolve_alias, role), + "123456789012/administrator") + def test_get_profile_accrole_name_do_not_resolve_alias_include_paths(self): "Testing the acc-role, without alias resolution, and including full role path" creds = GimmeAWSCreds()