Skip to content

Commit

Permalink
Merge pull request #438 from abarrak/master
Browse files Browse the repository at this point in the history
feat: support account name as cred_profile option
  • Loading branch information
epierce authored Nov 13, 2023
2 parents 094a329 + 77066b2 commit b5cafac
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 21 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` [<my 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.
Expand All @@ -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`: `<acct>-/some/path/administrator`. If `n`: `<acct>-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`
Expand Down Expand Up @@ -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))
Expand All @@ -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
Expand Down
7 changes: 4 additions & 3 deletions gimme_aws_creds/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'])
Expand Down Expand Up @@ -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."
Expand All @@ -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
Expand Down Expand Up @@ -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(
Expand Down
30 changes: 17 additions & 13 deletions gimme_aws_creds/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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={
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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,
Expand All @@ -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 = []
Expand Down Expand Up @@ -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
Expand Down
31 changes: 30 additions & 1 deletion tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand Down

0 comments on commit b5cafac

Please sign in to comment.