diff --git a/README.md b/README.md index b29482f..c4f75d7 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,8 @@ A configuration wizard will prompt you to enter the necessary configuration para - write_aws_creds - True or False - If True, the AWS credentials will be written to `~/.aws/credentials` otherwise it will be written to stdout. - 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-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` 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 - 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. @@ -225,7 +226,7 @@ A configuration wizard will prompt you to enter the necessary configuration para - email - OTP via email - web - DUO uses localhost webbrowser to support push|call|passcode - passcode - DUO uses `OKTA_MFA_CODE` or `--mfa-code` if set, or prompts user for passcode(OTP). - + - resolve_aws_alias - y or n. If yes, gimme-aws-creds will try to resolve AWS account ids with respective alias names (default: n). This option can also be set interactively in the command line using `-r` or `--resolve` parameter - include_path - (optional) Includes full role path to the role name in AWS credential profile name. (default: n). If `y`: `-/some/path/administrator`. If `n`: `-administrator` - remember_device - y or n. If yes, the MFA device will be remembered by Okta service for a limited time. This option can also be set interactively in the command line using `-m` or `--remember-device` @@ -374,7 +375,7 @@ for data in creds.iter_selected_aws_credentials(): if len(piece) == 12 and piece.isdigit(): account_id = piece break - + if account_id is None: raise ValueError("Didn't find aws_account_id (12 digits) in {}".format(arn)) @@ -390,7 +391,7 @@ gimme-aws-creds works both on FIDO1 enabled org and WebAuthN enabled org Note that FIDO1 will probably be deprecated in the near future as standards moves forward to WebAuthN WebAuthN support is available for usb security keys (gimme-aws-creds relies on the yubico fido2 lib). - + To use your local machine as an authenticator, along with Touch ID or Windows Hello, if available, you must register a new authenticator via gimme-aws-creds, using: ```bash diff --git a/gimme_aws_creds/config.py b/gimme_aws_creds/config.py index fc2c288..a7f2bfd 100644 --- a/gimme_aws_creds/config.py +++ b/gimme_aws_creds/config.py @@ -310,7 +310,7 @@ def update_config_file(self): config_dict['aws_default_duration'] = self._get_aws_default_duration(defaults['aws_default_duration']) if config_dict['gimme_creds_server'] != 'appurl': config_dict['aws_appname'] = self._get_aws_appname(defaults['aws_appname']) - + config_dict["output_format"] = '' if not config_dict["write_aws_creds"]: config_dict['output_format'] = self._get_output_format(defaults['output_format']) @@ -489,6 +489,7 @@ def _get_cred_profile(self, default_entry): ui.default.message( "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 '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." @@ -497,7 +498,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-role']: + if cred_profile.lower() in ['default', 'role', 'acc', 'acc-role']: cred_profile = cred_profile.lower() return cred_profile @@ -598,7 +599,7 @@ def _get_force_classic(self, default_entry): "Force classic login flow", default_entry) except ValueError: ui.default.warning("Force Classic login flow must be either y or n.") - + def _get_open_browser(self, default_entry): """Option to automatically open the default browser for OIE authentication""" ui.default.message( diff --git a/gimme_aws_creds/main.py b/gimme_aws_creds/main.py index f897f00..553ebd1 100644 --- a/gimme_aws_creds/main.py +++ b/gimme_aws_creds/main.py @@ -40,7 +40,7 @@ class GimmeAWSCreds(object): """ This is a CLI tool that gets temporary AWS credentials from Okta based on the available AWS Okta Apps and roles - assigned to the user. + assigned to the user. """ resolver = DefaultResolver() envvar_list = [ @@ -496,18 +496,18 @@ def output_format(self): def set_okta_platform(self, okta_platform): self._cache['okta_platform'] = okta_platform - + @property def okta_platform(self): if 'okta_platform' in self._cache: return self._cache['okta_platform'] - + # Treat this domain as classic, even if it's OIE if self.config.force_classic == True or self.conf_dict.get('force_classic') == "True": self.ui.message('Okta Classic login flow enabled') self.set_okta_platform('classic') return 'classic' - + response = requests.get( self.okta_org_url + '/.well-known/okta-organization', headers={ @@ -666,10 +666,10 @@ def aws_results(self): id_token=False, scopes=['openid'] ) - + # auth_session isn't needed when using gimme_creds_lambda and Okta classic self.set_auth_session(None) - + elif self.okta_platform == 'identity_engine': auth_result = self.auth_session @@ -800,14 +800,12 @@ def get_profile_name(self, cred_profile, include_path, naming_data, resolve_alia 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 = naming_data['account'] + account = self._get_account_name(naming_data['account'], role, resolve_alias) role_name = naming_data['role'] path = naming_data['path'] - if resolve_alias == 'True': - account_alias = self._get_alias_from_friendly_name(role.friendly_account_name) - if account_alias: - account = account_alias if include_path == 'True': role_name = ''.join([path, role_name]) profile_name = '-'.join([account, @@ -816,6 +814,12 @@ def get_profile_name(self, cred_profile, include_path, naming_data, resolve_alia profile_name = cred_profile return profile_name + def _get_account_name(self, account, role, resolve_alias): + if resolve_alias == "False": + return account + account_alias = self._get_alias_from_friendly_name(role.friendly_account_name) + return account_alias or account + def iter_selected_aws_credentials(self): results = [] aws_results = [] @@ -850,8 +854,8 @@ def _run(self): self.handle_setup_fido_authenticator() self.handle_action_store_json_creds() self.handle_action_list_roles() - - + + # for each data item, if we have an override on output, prioritize that # if we do not, prioritize writing credentials to file if that is in our # configuration. If we are not writing to a credentials file, use whatever diff --git a/tests/test_main.py b/tests/test_main.py index 0fb7f04..f236e7e 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -115,7 +115,7 @@ def test_get_partition_aws(self): partition, region = creds._get_partition_and_region_from_saml_acs('https://signin.aws.amazon.com/saml') self.assertEqual(partition, 'aws') self.assertEqual(region, 'us-east-1') - + def test_get_region_partition_aws(self): creds = GimmeAWSCreds() @@ -228,6 +228,7 @@ def test_get_profile_accrole_name_do_not_resolve_alias_include_paths(self): include_path = 'True' self.assertEqual(creds.get_profile_name(cred_profile, include_path, naming_data, resolve_alias, role), "123456789012-/some/long/extended/path/administrator") + def test_get_profile_name_role(self): "Testing the role" creds = GimmeAWSCreds() @@ -242,6 +243,34 @@ def test_get_profile_name_role(self): self.assertEqual(creds.get_profile_name(cred_profile, include_path, naming_data, resolve_alias, role), 'administrator') + def test_get_profile_name_account_resolve_alias(self): + "Testing the account with alias resolution" + 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' + resolve_alias = 'True' + include_path = 'True' + self.assertEqual(creds.get_profile_name(cred_profile, include_path, naming_data, resolve_alias, role), + 'my-org-master') + + def test_get_profile_name_account_do_not_resolve_alias(self): + "Testing the account without alias resolution" + 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' + resolve_alias = 'False' + include_path = 'True' + self.assertEqual(creds.get_profile_name(cred_profile, include_path, naming_data, resolve_alias, role), + '123456789012') + def test_get_profile_name_default(self): "Testing the default" creds = GimmeAWSCreds()