def get_saml_assertion(self, prompt: bool = False, mfa: Optional[str] = None) -> str: """ Lookup OKTA session from cache, if it's valid, use it, otherwise, generate new assertion with MFA Args: prompt: Used for forcing prompts of username / password and always generating a new assertion mfa: MFA to use for generating the new OKTA session with. force_new: Forces a new session, abandons one from cache """ log.info(f'Getting SAML assertion. Provided MFA override: {mfa}') invalid_session = True okta = self.get_sso_session(prompt, mfa) failure_count = 0 # Todo: is this an infinite loop after a request from UI with a bad MFA? while invalid_session: try: assertion = okta.get_assertion() except InvalidSessionError as e: if failure_count > 0: print(e) print( "Authentication failed with SSO provider, please reauthenticate" " Likely invalid MFA or Password?\r\n") failure_count += 1 log.debug(f" invalid session: {e}") user = self._get_user(prompt) password = self._get_password(user, prompt=prompt, save=True) if self._defaults.mfa_enabled: color = Utils.default_colors( ) if self._defaults.colors_enabled else None mfa = self._secrets_mgr.get_next_mfa(user) if self._defaults.auto_mfa else \ Input.get_mfa(display_hint=True, color=color) else: mfa = None primary_auth = OktaPrimaryAuth(self._defaults, password, mfa) try: print("Trying to write session to cache...") self._write_okta_session_to_cache( primary_auth.get_session()) except InvalidSessionError as e: print(f"Got invalid session: {e}") return self.get_saml_assertion(prompt=True) else: return self.get_saml_assertion(prompt=True) else: assertion = base64.b64decode(assertion).decode('utf-8') self._saml_cache.write(SAML_ASSERTION_CACHE_KEY, assertion) return assertion
def handle_totp(self, sess): response_page = BeautifulSoup(sess.text, 'html.parser') tl = response_page.find('input', {'name': 'TL'}).get('value') gxf = response_page.find('input', {'name': 'gxf'}).get('value') challenge_url = sess.url.split("?")[0] challenge_id = challenge_url.split("totp/")[1] if self._defaults.mfa_enabled: color = Utils.default_colors( ) if self._defaults.colors_enabled else None mfa_token = self._secrets_mgr.get_next_mfa(self._defaults.user) if self._defaults.auto_mfa else \ Input.get_mfa(display_hint=True, color=color) else: mfa_token = None if not mfa_token: raise ValueError( "MFA token required for {} but none supplied.".format( self.config.username)) payload = { 'challengeId': challenge_id, 'challengeType': 6, 'continue': self.cont, 'scc': 1, 'sarp': 1, 'checkedDomains': 'youtube', 'pstMsg': 0, 'TL': tl, 'gxf': gxf, 'Pin': mfa_token, 'TrustDevice': 'on', } # Submit TOTP return self.post(challenge_url, data=payload)
def get_sso_session(self, prompt: bool = False, mfa: Optional[str] = None) -> Okta: """ Pulls the last okta session from cache, if cache doesn't exist, generates a new session and writes it to cache. From this session, the OKTA SVC is hydrated and returned. Args: prompt: If supplied, will never get session from cache. Returns: Initialized Okta service. """ count = 0 while True: try: if prompt: raise InvalidSessionError( "Forcing new session due to prompt.") cached_session = self._get_session_from_cache() if not cached_session: raise InvalidSessionError("No session found in cache.") okta = Okta(OktaSessionAuth(self._defaults, cached_session)) return okta except (FileNotFoundError, InvalidSessionError, JSONDecodeError, AttributeError) as e: try: password = self._secrets_mgr.get_password( self._defaults.user) if not mfa: if self._context.command == commands.ui and not self._defaults.auto_mfa: raise CannotRetrieveMFAException( "Cannot retrieve MFA, UI mode is activated.") else: color = Utils.default_colors( ) if self._defaults.colors_enabled else None mfa = self._secrets_mgr.get_next_mfa(self._defaults.user) if self._defaults.auto_mfa else \ Input.get_mfa(display_hint=True, color=color) log.info(f"Getting OKTA primary auth with mfa: {mfa}") primary_auth = OktaPrimaryAuth(self._defaults, password, mfa) self._write_okta_session_to_cache( primary_auth.get_session()) return Okta(primary_auth) except InvalidSessionError as e: prompt = True log.error( f"Caught error when authing with OKTA & caching session: {e}. " ) time.sleep(1) count += 1 if count > 1: if self._context.command == ui: raise InvalidCredentialsException( "Failed OKTA authentication. Invalid user, password, or MFA." ) else: Utils.stc_error_exit( "Unable to autheticate with OKTA with your provided credentials. Perhaps your" f"user, password, or MFA changed? Try rerunning `{CLI_NAME} --configure` again." )
def get_session(self, env: GlobalEnvironment, prompt: bool, exit_on_fail=True, mfa: Optional[str] = None) -> boto3.Session: forced = False log.info( f"Getting session for role: {env.role.role_arn} in env: {env.role.run_env.env}" ) attempts = 0 while True: try: if prompt and not forced: forced = True raise InvalidSessionError( "Forcing new session due to prompt.") creds: FiggyAWSSession = self._sts_cache.get_val( env.role.cache_key()) if creds: session = boto3.Session( aws_access_key_id=creds.access_key, aws_secret_access_key=creds.secret_key, aws_session_token=creds.token, region_name=env.region) if creds.expires_soon( ) or not self._is_valid_session(session): self._utils.validate( attempts < self._MAX_ATTEMPTS, f"Failed to authenticate with AWS after {attempts} attempts. Exiting. " ) attempts = attempts + 1 log.info( "Invalid session detected in cache. Raising session error." ) raise InvalidSessionError("Invalid Session Detected") log.info("Valid bastion SSO session returned from cache.") return session else: raise InvalidSessionError( "Forcing new session, cache is empty.") except (FileNotFoundError, NoCredentialsError, InvalidSessionError) as e: try: if self._defaults.mfa_enabled: self._defaults.mfa_serial = self.get_mfa_serial() color = Utils.default_colors( ) if self._defaults.colors_enabled else None if not mfa: if self._context.command == commands.ui and not self._defaults.auto_mfa: raise CannotRetrieveMFAException( "Cannot retrieve MFA, UI mode is activated." ) else: mfa = self._secrets_mgr.get_next_mfa(self._defaults.user) if self._defaults.auto_mfa else \ Input.get_mfa(display_hint=True, color=color) response = self.__get_sts().assume_role( RoleArn=env.role.role_arn, RoleSessionName=Utils.sanitize_session_name( self._defaults.user), DurationSeconds=self._defaults.session_duration, SerialNumber=self._defaults.mfa_serial, TokenCode=mfa) else: response = self.__get_sts().assume_role( RoleArn=env.role.role_arn, RoleSessionName=Utils.sanitize_session_name( self._defaults.user), DurationSeconds=self._defaults.session_duration) session = FiggyAWSSession( **response.get('Credentials', {})) log.info(f"Got session response: {response}") self._sts_cache.write(env.role.cache_key(), session) except (ClientError, ParamValidationError) as e: if isinstance( e, ParamValidationError ) or "AccessDenied" == e.response['Error']['Code']: if exit_on_fail: self._utils.error_exit( f"Error authenticating with AWS from Bastion Profile:" f" {self._defaults.provider_config.profile_name}: {e}" ) else: if exit_on_fail: log.error( f"Failed to authenticate due to error: {e}") self._utils.error_exit( f"Error getting session for role: {env.role.role_arn} " f"-- Are you sure you have permissions?") raise e