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
Beispiel #2
0
    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