Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the use of system keychain optional #440

Merged
merged 2 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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