Skip to content

Commit

Permalink
Merge pull request #440 from Nike-Inc/dev
Browse files Browse the repository at this point in the history
Make the use of system keychain optional
  • Loading branch information
epierce authored Nov 20, 2023
2 parents b5cafac + f6b6401 commit 72c2074
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 6 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ A configuration wizard will prompt you to enter the necessary configuration para
- aws_default_duration = This is optional. Lifetime for temporary credentials, in seconds. Defaults to 1 hour (3600)
- app_url - If using 'appurl' setting for gimme_creds_server, this sets the url to the aws application configured in Okta. It is typically something like <https://something.okta[preview].com/home/amazon_aws/app_instance_id/something>
- okta_username - use this username to authenticate
- enable_keychain - enable the use of the system keychain to store the user's password
- preferred_mfa_type - automatically select a particular device when prompted for MFA:
- push - Okta Verify App push or DUO push (depends on okta supplied provider type)
- token:software:totp - OTP using the Okta Verify App
Expand Down
27 changes: 26 additions & 1 deletion gimme_aws_creds/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def __init__(self, gac_ui, create_config=True):
'OKTA_CONFIG',
os.path.join(self.FILE_ROOT, '.okta_aws_login_config')
)
self.disable_keychain = False
self.open_browser = False
self.action_register_device = False
self.username = None
Expand Down Expand Up @@ -152,6 +153,10 @@ def get_args(self):
'--open-browser', action='store_true',
help='Automatically open a webbrowser for device authorization (Okta Identity Engine only)'
)
parser.add_argument(
'--disable-keychain', action='store_true',
help="Disable the use of the system keychain to store the user's password"
)
parser.add_argument(
'--force-classic', action='store_true',
help='Force the use of the Okta Classic login process (Okta Identity Engine only)'
Expand All @@ -165,6 +170,7 @@ def get_args(self):
self.action_register_device = args.action_register_device
self.action_setup_fido_authenticator = args.action_setup_fido_authenticator
self.open_browser = args.open_browser
self.disable_keychain = args.disable_keychain
self.force_classic = args.force_classic

if args.insecure is True:
Expand All @@ -189,6 +195,13 @@ def get_args(self):
self.conf_profile = args.profile or 'DEFAULT'

def _handle_config(self, config, profile_config, include_inherits = True):
# Convert True/False strings to booleans
for key in profile_config:
if profile_config[key] == 'True':
profile_config[key] = True
elif profile_config[key] == 'False':
profile_config[key] = False

if "inherits" in profile_config.keys() and include_inherits:
self.ui.message("Using inherited config: " + profile_config["inherits"])
if profile_config["inherits"] not in config:
Expand Down Expand Up @@ -238,6 +251,7 @@ def update_config_file(self):
aws_default_duration = Default AWS session duration (3600)
preferred_mfa_type = Select this MFA device type automatically
include_path - (optional) includes that full role path to the role name for profile
enable_keychain = (optional) enable the use of the system keychain to store the user's password
"""
config = configparser.ConfigParser()
Expand All @@ -262,7 +276,8 @@ def update_config_file(self):
'aws_default_duration': '3600',
'output_format': 'export',
'force_classic': '',
'open_browser': ''
'open_browser': '',
'enable_keychain': 'y'
}

# See if a config file already exists.
Expand Down Expand Up @@ -292,6 +307,7 @@ def update_config_file(self):
# These options are only used in the Classic authentication flow
if self._okta_platform == 'classic' or config_dict['force_classic'] is True:
config_dict['okta_username'] = self._get_okta_username(defaults['okta_username'])
config_dict['enable_keychain'] = self._get_enable_keychain(defaults['enable_keychain'])
config_dict['preferred_mfa_type'] = self._get_preferred_mfa_type(defaults['preferred_mfa_type'])
config_dict['remember_device'] = self._get_remember_device(defaults['remember_device'])

Expand Down Expand Up @@ -387,6 +403,15 @@ def _get_auth_server_entry(self, default_entry):
self._okta_auth_server = okta_auth_server

return okta_auth_server

def _get_enable_keychain(self, default_entry):
""" enable the use of the system keychain to store the user's password """

while True:
try:
return self._get_user_input_yes_no("Use the system keychain to store the user's password? (y/n)", default_entry)
except ValueError:
ui.default.warning("Enable keychain must be either y or n.")

def _get_client_id_entry(self, default_entry):
""" Get and validate client_id """
Expand Down
4 changes: 4 additions & 0 deletions gimme_aws_creds/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,9 @@ def generate_config(self):
config.get_args()
self._cache['conf_dict'] = config.get_config_dict()

if config.disable_keychain is True:
self.conf_dict['enable_keychain'] = False

for value in self.envvar_list:
if self.ui.environ.get(value):
key = self.envvar_conf_map.get(value, value).lower()
Expand Down Expand Up @@ -566,6 +569,7 @@ def okta(self):
self.okta_org_url,
self.config.verify_ssl_certs,
self.device_token,
self.conf_dict.get('enable_keychain', True)
)

if self.config.username is not None:
Expand Down
10 changes: 6 additions & 4 deletions gimme_aws_creds/okta_classic.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class OktaClassicClient(object):
KEYRING_SERVICE = 'gimme-aws-creds'
KEYRING_ENABLED = not isinstance(keyring.get_keyring(), FailKeyring)

def __init__(self, gac_ui, okta_org_url, verify_ssl_certs=True, device_token=None):
def __init__(self, gac_ui, okta_org_url, verify_ssl_certs=True, device_token=None, use_keyring=True):
"""
:type gac_ui: ui.UserInterface
:param okta_org_url: Base URL string for Okta IDP.
Expand All @@ -56,6 +56,8 @@ def __init__(self, gac_ui, okta_org_url, verify_ssl_certs=True, device_token=Non
self._okta_org_url = okta_org_url
self._verify_ssl_certs = verify_ssl_certs

self._use_keyring = use_keyring

if verify_ssl_certs is False:
requests.packages.urllib3.disable_warnings()

Expand Down Expand Up @@ -357,7 +359,7 @@ def _login_username_password(self, state_token, url):
# ref: https://developer.okta.com/docs/reference/error-codes/#example-errors-listed-by-http-return-code
elif response.status_code in [400, 401, 403, 404, 409, 429, 500, 501, 503]:
if response_data['errorCode'] == "E0000004":
if self.KEYRING_ENABLED:
if self.KEYRING_ENABLED and self._use_keyring:
try:
self.ui.info("Stored password is invalid, clearing. Please try again")
keyring.delete_password(self.KEYRING_SERVICE, creds['username'])
Expand Down Expand Up @@ -901,7 +903,7 @@ def _get_username_password_creds(self):
username = self._username

password = self._password
if not password and self.KEYRING_ENABLED:
if not password and self.KEYRING_ENABLED and self._use_keyring:
try:
# If the OS supports a keyring, offer to save the password
password = keyring.get_password(self.KEYRING_SERVICE, username)
Expand All @@ -917,7 +919,7 @@ def _get_username_password_creds(self):
if len(password) > 0:
break

if self.KEYRING_ENABLED:
if self.KEYRING_ENABLED and self._use_keyring:
# If the OS supports a keyring, offer to save the password
if self.ui.input("Do you want to save this password in the keyring? (y/N) ").lower() == 'y':
try:
Expand Down
3 changes: 2 additions & 1 deletion tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ def tearDown(self):
action_store_json_creds=False,
action_setup_fido_authenticator=False,
open_browser=False,
force_classic=False
force_classic=False,
disable_keychain=False
),
)
def test_get_args_username(self, mock_arg):
Expand Down

0 comments on commit 72c2074

Please sign in to comment.