Nike-Inc / gimme-aws-creds

A CLI that utilizes Okta IdP via SAML to acquire temporary AWS credentials
Apache License 2.0
919 stars 262 forks source link

Support for Okta + Duo Universal Prompt #431

Closed aogail closed 7 months ago

aogail commented 11 months ago

I've integrated Duo Universal Prompt with our Okta tenant. As a user who has enrolled in the universal prompt MFA, using gimme-aws-creds fails because it appears that Universal Prompt requires different interactions than the traditional prompt.

I am interested in contributing support for Duo Universal Prompt. I'm posting this issue, before jumping in fully, to check that this isn't already being worked on, and would be a welcome contribution. I haven't found any references to Universal Prompt in existing issues; hopefully I didn't miss one.

Expected Behavior

gimme-aws-creds against an Okta classic (and OIE?) tenant with Duo Universal Prompt MFA completes the authentication flow and obtains functional AWS credentials. In particular, I'm interested in use cases where:

Current Behavior

The current implementation results in the following exception when the user's only MFA type is a Duo Universal Prompt integration:

 $ gimme-aws-creds         
Username: xxxx
Using password from keyring for xxxx
Okta Password for xxxx: 
Do you want to save this password in the keyring? (y/N) 
Multi-factor Authentication required.
Preferred factor type of push not available.
Using the only authentication factor configured: Unknown MFA type: claims_provider.
Traceback (most recent call last):
  File "/usr/local/bin/gimme-aws-creds", line 17, in <module>
    GimmeAWSCreds().run()
  File "/usr/local/Cellar/gimme-aws-creds/2.7.2/libexec/lib/python3.11/site-packages/gimme_aws_creds/main.py", line 451, in run
    self._run()
  File "/usr/local/Cellar/gimme-aws-creds/2.7.2/libexec/lib/python3.11/site-packages/gimme_aws_creds/main.py", line 859, in _run
    for data in self.iter_selected_aws_credentials():
  File "/usr/local/Cellar/gimme-aws-creds/2.7.2/libexec/lib/python3.11/site-packages/gimme_aws_creds/main.py", line 828, in iter_selected_aws_credentials
    aws_results = executor.map(generate_credentials_prepare_data, self.aws_selected_roles)
                                                                  ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/Cellar/gimme-aws-creds/2.7.2/libexec/lib/python3.11/site-packages/gimme_aws_creds/main.py", line 714, in aws_selected_roles
    selected_roles = self._get_selected_roles(self.requested_roles, self.aws_roles)
                                                                    ^^^^^^^^^^^^^^
  File "/usr/local/Cellar/gimme-aws-creds/2.7.2/libexec/lib/python3.11/site-packages/gimme_aws_creds/main.py", line 705, in aws_roles
    self.saml_data['SAMLResponse'],
    ^^^^^^^^^^^^^^
  File "/usr/local/Cellar/gimme-aws-creds/2.7.2/libexec/lib/python3.11/site-packages/gimme_aws_creds/main.py", line 696, in saml_data
    self._cache['saml_data'] = saml_data = self.okta.get_saml_response(self.aws_app['links']['appLink'], self.auth_session)
                                                                       ^^^^^^^^^^^^
  File "/usr/local/Cellar/gimme-aws-creds/2.7.2/libexec/lib/python3.11/site-packages/gimme_aws_creds/main.py", line 689, in aws_app
    self._cache['aws_app'] = aws_app = self._get_selected_app(self.conf_dict.get('aws_appname'), self.aws_results)
                                                                                                 ^^^^^^^^^^^^^^^^
  File "/usr/local/Cellar/gimme-aws-creds/2.7.2/libexec/lib/python3.11/site-packages/gimme_aws_creds/main.py", line 635, in aws_results
    self.auth_session
  File "/usr/local/Cellar/gimme-aws-creds/2.7.2/libexec/lib/python3.11/site-packages/gimme_aws_creds/main.py", line 616, in auth_session
    auth_result = self.okta.auth_session(redirect_uri=self.conf_dict.get('app_url'), open_browser=open_browser)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/Cellar/gimme-aws-creds/2.7.2/libexec/lib/python3.11/site-packages/gimme_aws_creds/okta_classic.py", line 160, in auth_session
    login_response = self.auth()
                     ^^^^^^^^^^^
  File "/usr/local/Cellar/gimme-aws-creds/2.7.2/libexec/lib/python3.11/site-packages/gimme_aws_creds/okta_classic.py", line 151, in auth
    while flow_state.get('apiResponse', {}).get('status') != 'SUCCESS':
          ^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'get'

Possible Solution

I haven't gotten far enough into my investigation to have a clear implementation path, yet.

Steps to Reproduce (for bugs)

  1. Start with an Okta Classic tenant integrated with AWS account federation
  2. Set up Duo Universal Prompt with Okta per https://duo.com/docs/okta#duo-custom-idp-factor-in-okta-classic
  3. Enroll user account with the Duo Universal Prompt factor in Okta
  4. Configure gimme-aws-creds for the target Okta
  5. Attempt to use gimme-aws-creds to gain AWS access

Context

Duo is sunsetting their Traditional prompt in March 2024, so we need to migrate completely to Universal Prompt by then. Many of our people utilize gimme-aws-creds in their daily workflows, so I'm interested in contributing support for it.

Your Environment

epierce commented 7 months ago

Fix was merged today and will be included in next release

aogail commented 7 months ago

Thanks @epierce! Do you know when you plan to make the next release? I'm planning communications with my team about the traditional prompt being disabled soon and may or may not need to include "run gimme-aws-creds from the master branch until the next release" ;)

epierce commented 7 months ago

Probably late next week - I want to go through the recent issues and see if there's anything that can be cleared up quickly and then it needs to go through our change management procedures here.