Beispiel #1
0
    def __saml_assertion_aws(self, session_token, configuration):
        ''' fetch saml 2.0 assertion '''
        response = self.__okta_app_response(session_token=session_token,
                                            configuration=configuration)

        if self.verbose:
            Common.dump_verbose(
                message='SAML response {} session token: {}'.format(
                    "with" if session_token else "without", response.content))

        soup = BeautifulSoup(response.content, "html.parser")
        assertion = None
        for inputtag in soup.find_all('input'):
            if inputtag.get('name') == 'SAMLResponse':
                assertion = inputtag.get('value')
        # If a session token is not passed in, we consider a failure as a normal possibility and just return None
        if not assertion and session_token:
            if self.verbose:
                Common.dump_verbose(
                    'Expecting \'<input name="SAMLResponse" value="...">\' in Okta response, but not found.'
                )
            Common.dump_err(message='Unexpected response from Okta.',
                            exit_code=4,
                            verbose=self.verbose)
        return assertion
Beispiel #2
0
    def verify_preferred_factor(self):
        """ Return the Okta MFA configuration for the matching, supported configuration """
        preferred_factors = [
            opt for opt in self.option_factors
            if self.factor_preference == opt['prompt']
        ]
        if preferred_factors:
            if Common.is_debug():
                msg = 'Using preferred factor: {}'.format(
                    self.factor_preference)
                Common.dump_out(message=msg)

            matching_okta_factor = [
                fact for fact in self.okta_factors
                if fact['provider'] == preferred_factors[0]['provider']
                and fact['factorType'] == preferred_factors[0]['factor_type']
            ]

            return matching_okta_factor[0]

        else:
            msg = 'The MFA option \'{}\' in your configuration file is not available.\nAvailable options are {}'.format(
                self.factor_preference,
                [opt['prompt'] for opt in self.option_factors])
            Common.dump_err(message=msg)
            raise ValueError("Unexpected MFA option")  # TODO: Reprompt
Beispiel #3
0
    def generate_configuration(cls, config_section, verbose=False):
        configuration = {}
        for field in ConfigGenerator.config_fields:
            key = field['name']
            is_secret = 'secret' in field and field['secret']
            from_env = os.getenv(key=key, default=-1)
            if from_env != -1:
                # If defined in environment, use that first
                configuration[key] = from_env
            elif key in config_section and config_section[key]:
                # If defined in the config file, make sure it's not a secret, otherwise use it
                if is_secret:
                    Common.dump_err(
                        message=
                        'Invalid configuration.  {} should never be defined in clokta.cfg.'
                        .format(key),
                        exit_code=6,
                        verbose=False)
                configuration[key] = config_section[key]
            elif 'required' in field and field['required']:
                # We need it.  Prompt for it.
                configuration[key] = ConfigGenerator.prompt_for(key)

        if verbose:
            copy_config = copy.deepcopy(configuration)
            for key in copy_config:
                if key.endswith('password'):
                    copy_config[key] = '<redacted>'
            msg = 'Configuration: {}'.format(
                json.dumps(obj=copy_config, indent=4))
            Common.dump_verbose(message=msg)

        return configuration
Beispiel #4
0
    def choose_idp_role_tuple(self):
        ''' Determine the role options the user can choose from '''
        idp_role_tuples = self.__discover_role_idp_tuples()

        # throw an error if no roles are provided
        #  (defensive coding only - this should not be impossible)
        if not idp_role_tuples:
            Common.dump_err(
                message='No AWS Role was assigned to this application!',
                exit_code=4,
                verbose=self.verbose)

        # use the one prvided if there is only one
        if len(idp_role_tuples) == 1:
            role_arn = idp_role_tuples[0][2]
            if self.role_preference and role_arn != self.role_preference:
                Common.echo(
                    message='Your cofigured role was not found; using {role}'.
                    format(role=role_arn))
            else:
                Common.echo(message='Using the configured role {role}'.format(
                    role=role_arn))
            return idp_role_tuples[0]

        # use the configured role if it matches one from the the SAML assertion
        for tup in idp_role_tuples:
            if tup[2] == self.role_preference:
                return tup

        # make the user choose
        return self.__choose_tuple(idp_role_tuples=idp_role_tuples)
Beispiel #5
0
    def __saml_assertion_aws(self, session_token, configuration):
        ''' fetch saml 2.0 assertion '''
        response = self.__okta_app_response(
            session_token=session_token,
            configuration=configuration
        )

        if self.verbose:
            Common.dump_verbose(
                message='SAML response: {}'.format(response.content)
            )

        soup = BeautifulSoup(response.content, "html.parser")
        assertion = None
        for inputtag in soup.find_all('input'):
            if inputtag.get('name') == 'SAMLResponse':
                assertion = inputtag.get('value')
        if not assertion:
            if self.verbose:
                Common.dump_verbose('Expecting \'<input name="SAMLResponse" value="...">\' in Okta response, but not found.')
            Common.dump_err(
                message='Unexpected response from Okta.',
                exit_code=4,
                verbose=self.verbose
            )
        return assertion
Beispiel #6
0
    def __initialize_configuration(self):
        """
        Load config file, both the desired section and the default section
        :return: the parameter list
        :rtype: dict
        """
        clokta_cfg_file = configparser.ConfigParser()
        clokta_cfg_file.read(self.clokta_config_file)

        # Make sure we have the bare minimum in the config file.  The DEFAULT section and the app URL.
        if not clokta_cfg_file['DEFAULT']:
            clokta_cfg_file['DEFAULT'] = {'okta_org': ''}
        if not clokta_cfg_file.has_section(self.profile_name):
            msg = 'No profile "{}" in clokta.cfg, but enter the information and clokta will create a profile.\n' + \
                  'Copy the link from the Okta App'
            app_url = click.prompt(text=msg.format(self.profile_name),
                                   type=str,
                                   err=Common.to_std_error()).strip()
            if not app_url.startswith("https://") or not app_url.endswith(
                    "?fromHome=true"):
                Common.dump_err(
                    "Invalid App URL.  URL usually of the form https://xxxxxxxx.okta.com/.../272?fromHome=true",
                    6)
                raise ValueError("Invalid URL")
            else:
                app_url = app_url[:-len("?fromHome=true")]
            clokta_cfg_file.add_section(self.profile_name)
            clokta_cfg_file.set(self.profile_name, 'okta_aws_app_url', app_url)

        config_section = clokta_cfg_file[self.profile_name]
        self.__load_parameters(config_section)
        if self.get('save_password_in_keychain') == 'True':
            self.parameters[
                'okta_password'].save_to = ConfigParameter.SaveTo.KEYRING
Beispiel #7
0
    def __check_push_result(self, state_token, push_response):
        ''' Wait for push response acknowledgement '''
        url = push_response['_links']['next']['href']
        headers = {
            'Accept': 'application/json',
            'Content-Type': 'application/json',
            'Cache-Control': '"no-cache'
        }
        payload = {
            'stateToken': state_token
        }

        wait_for = 60
        timeout = time.time() + wait_for
        response_data = None
        while True:
            Common.echo(message='.', new_line=False)
            response = requests.post(url, data=json.dumps(payload), headers=headers)
            if response.status_code == requests.codes.ok:  # pylint: disable=E1101
                response_data = response.json()
            else:
                response.raise_for_status()

            if 'sessionToken' in response_data or time.time() > timeout:
                Common.echo(message='Session confirmed')
                break
            time.sleep(3)

        if response_data:
            return response_data['sessionToken']
        else:
            msg = 'Timeout expired ({} seconds)'.format(wait_for)
            Common.dump_err(message=msg, exit_code=3)
Beispiel #8
0
    def initialize_configuration(self):
        ''' Generate and load config file section '''
        parser = configparser.ConfigParser()
        parser.read(self.config_location)

        if not parser['DEFAULT']:
            parser['DEFAULT'] = {'okta_org': ''}

        if self.profile_name not in parser.sections():
            msg = 'No profile "{}" in clokta.cfg, but enter the information and clokta will create a profile.\nCopy the link from the Okta App'
            app_url = click.prompt(msg.format(self.profile_name),
                                   type=str).strip()
            if not app_url.startswith("https://") or not app_url.endswith(
                    "?fromHome=true"):
                Common.dump_err(
                    "Invalid App URL.  URL usually of the form https://xxxxxxxx.okta.com/.../272?fromHome=true",
                    6, False)
            else:
                app_url = app_url[:-len("?fromHome=true")]
            parser[self.profile_name] = {'okta_aws_app_url': app_url}

        config_section = parser[self.profile_name]
        updated_config = ConfigGenerator.generate_configuration(
            config_section=config_section, verbose=self.verbose)
        self.__write_config(path_to_file=self.config_location, parser=parser)
        return updated_config
Beispiel #9
0
def __okta_session_token_mfa(auth_response,
                             factors,
                             factor_preference,
                             verbose=False):
    ''' Determine which factor to use and apply it to get a session token '''
    factor = __choose_factor(factors=factors,
                             factor_preference=factor_preference,
                             verbose=verbose)
    state_token = auth_response['stateToken']

    if factor['factorType'] == 'push':
        return __send_push(factor=factor, state_token=state_token)

    if factor['factorType'] == 'sms':
        __okta_mfa_verification(factor_dict=factor,
                                state_token=state_token,
                                otp_value=None)

    otp_value = click.prompt('Enter your multifactor authentication token',
                             type=str)
    try:
        mfa_response = __okta_mfa_verification(factor_dict=factor,
                                               state_token=state_token,
                                               otp_value=otp_value)
        session_token = mfa_response['sessionToken']
    except requests.exceptions.HTTPError as http_err:
        msg = 'Okta returned this MFA related error: {}'.format(http_err)
        Common.dump_err(message=msg, exit_code=1, verbose=verbose)
    except Exception as err:
        msg = 'Unexpected error: {}'.format(err)
        Common.dump_err(message=msg, exit_code=2, verbose=verbose)

    return session_token
Beispiel #10
0
    def assume_role(self, reset_default_role):
        """
        entry point for the cli tool
        :param reset_default_role: whether to reset whatever the default role is for this profile
        :type reset_default_role: bool
        """
        clokta_config_file = self.data_dir + "clokta.cfg"

        clokta_config = CloktaConfiguration(profile_name=self.profile,
                                            clokta_config_file=clokta_config_file)
        if reset_default_role:
            clokta_config.reset_default_role()

        # Attempt to initiate a connection using just cookies
        okta_initiator = OktaInitiator(data_dir=self.data_dir)
        result = okta_initiator.initiate_with_cookie(clokta_config)

        # If the cookie is expired or non-existent, INPUT_ERROR will be returned
        if result == OktaInitiator.Result.INPUT_ERROR:
            prompt_for_password = clokta_config.get('okta_password') is None
            mfas = []
            # Cookie didn't work.  Authenticate with Okta
            while result == OktaInitiator.Result.INPUT_ERROR:
                if prompt_for_password:
                    clokta_config.prompt_for(param_name='okta_password')
                result = okta_initiator.initiate_with_auth(clokta_config, mfas)
                if result == OktaInitiator.Result.INPUT_ERROR:
                    if prompt_for_password:
                        Common.dump_err("Failure.  Wrong password or misconfigured session.")
                    else:
                        Common.dump_err("Saved password may be out of date.")
                prompt_for_password = True

            if result == OktaInitiator.Result.NEED_MFA:
                done = False
                first_time = True
                while not done:
                    chosen_factor = clokta_config.determine_mfa_mechanism(mfas, force_prompt=not first_time)
                    need_otp = okta_initiator.initiate_mfa(factor=chosen_factor)
                    otp = clokta_config.determine_okta_onetimepassword(chosen_factor, first_time) if need_otp else None
                    result = okta_initiator.finalize_mfa(clokta_config=clokta_config, factor=chosen_factor, otp=otp)
                    done = result == OktaInitiator.Result.SUCCESS
                    first_time = False

        saml_assertion = okta_initiator.saml_assertion

        # We now have a SAML assertion and can generate a AWS Credentials
        aws_svc = AwsCredentialsGenerator(clokta_config=clokta_config,
                                          saml_assertion=saml_assertion,
                                          data_dir=self.data_dir)
        roles = aws_svc.get_roles()
        role = clokta_config.determine_role(roles)
        aws_svc.generate_creds(role)
        clokta_config.update_configuration()

        self.output_instructions(docker_file=aws_svc.docker_file, bash_file=aws_svc.bash_file)
Beispiel #11
0
def __okta_session_token(configuration, verbose=False):
    ''' Authenticate with Okta; receive a session token '''
    okta_response = None

    try:
        okta_response = __okta_auth_response(configuration=configuration)
    except requests.exceptions.HTTPError as http_err:
        msg = 'Okta returned this credentials/password related error: {}'.format(
            http_err)
        Common.dump_err(message=msg, exit_code=1, verbose=verbose)
    except Exception as err:
        msg = 'Unexpected error: {}'.format(err)
        Common.dump_err(message=msg, exit_code=2, verbose=verbose)

    # handle case where MFA is required but no factors have been enabled
    if okta_response['status'] == 'MFA_ENROLL':
        msg = 'Please enroll in multi-factor authentication before using this tool'
        Common.dump_err(message=msg, exit_code=3, verbose=verbose)

    if okta_response['status'] == 'MFA_REQUIRED':
        factors = okta_response['_embedded']['factors']
        if factors:
            return __okta_session_token_mfa(
                auth_response=okta_response,
                factors=factors,
                factor_preference=configuration['multifactor_preference'],
                verbose=verbose)
        else:
            msg = 'No MFA factors have been set up for this account'
            Common.dump_err(message=msg, exit_code=3, verbose=verbose)

    return okta_response['sessionToken']
Beispiel #12
0
    def __okta_session_token(self, configuration):
        ''' Authenticate with Okta; receive a session token '''
        okta_response = None

        try:
            okta_response = self.__okta_auth_response(
                configuration=configuration)
        except requests.exceptions.HTTPError as http_err:
            if self.verbose:
                msg = 'Okta returned this credentials/password related error: {}\nThis could be a mistyped password or a misconfigured username or URL.'.format(
                    http_err)
            else:
                msg = "Failure.  Wrong password or misconfigured session."
            Common.dump_err(message=msg, exit_code=1, verbose=self.verbose)
        except Exception as err:
            msg = 'Unexpected error: {}'.format(err)
            Common.dump_err(message=msg, exit_code=2, verbose=self.verbose)

        # handle case where MFA is required but no factors have been enabled
        if okta_response['status'] == 'MFA_ENROLL':
            msg = 'Please enroll in multi-factor authentication before using this tool'
            Common.dump_err(message=msg, exit_code=3, verbose=self.verbose)

        otp_value = None
        if configuration.get('okta_onetimepassword_secret'):
            try:
                import onetimepass as otp
            except ImportError:
                msg = 'okta_onetimepassword_secret provided in config but "onetimepass" is not installed. run: pip install onetimepass'
                Common.dump_err(message=msg, exit_code=3, verbose=self.verbose)
            otp_value = otp.get_totp(
                configuration['okta_onetimepassword_secret'])

        if okta_response['status'] == 'MFA_REQUIRED':
            factors = okta_response['_embedded']['factors']
            if factors:
                return self.__okta_session_token_mfa(
                    auth_response=okta_response,
                    factors=factors,
                    factor_preference=configuration['multifactor_preference'],
                    otp_value=otp_value)
            else:
                msg = 'No MFA factors have been set up for this account'
                Common.dump_err(message=msg, exit_code=3, verbose=self.verbose)

        return okta_response['sessionToken']
Beispiel #13
0
    def choose_role(self):
        """
        Look for a default role defined and, if not, prompt the user for one
        Allow the user to also specify the role is the default to be used
        from now on
        :return: a tuple of the chosen role and whether it is the
        new default
        :rtype: AwsRole, bool
        """
        # throw an error if no roles are provided
        #  (defensive coding only - this should not be possible)
        if not self.possible_roles:
            Common.dump_err(
                message='No AWS Role was assigned to this application!')
            raise ValueError(
                'Unexpected configuration - No AWS role assigned to Okta login.'
            )

        # use the one provided if there is only one
        if len(self.possible_roles) == 1:
            role = self.possible_roles[0]
            if self.role_preference and role.role_arn != self.role_preference:
                Common.dump_err(
                    message=
                    'Your cofigured role "{notfound}" was not found; using "{found}" role'
                    .format(notfound=self.role_preference,
                            found=role.role_name))
            elif Common.is_debug():
                Common.echo(message="Using default role '{role}'".format(
                    role=role.role_arn))
            return role, False

        # use the configured role if it matches one from the the SAML assertion
        for role in self.possible_roles:
            if role.role_arn == self.role_preference:
                message = "Using default role '{}'".format(role.role_name)
                extra_message = '.  Run "clokta --no-default-role" to override.'
                if Common.get_output_format() == Common.long_out:
                    Common.echo(message + extra_message)
                else:
                    Common.echo(message)
                return role, True

        # make the user choose
        return self.__prompt_for_role(with_set_default_option=True)
Beispiel #14
0
    def __load_parameters(self, config_section):
        """
        For each parameter this will look first in the OS environment, then in the
        config file (which will look in both the section and in the DEFAULT section),
        then in the keychain (for secrets only) and then
        if still not found and the attribute is required, will prompt the user.
        :param config_section: section of the clokta.cfg file that represents the profile that we will
        login to though queries on this will also look in the DEFAULT section
        :type config_section:
        :return: a map of attributes that define the clokta login, e.g.
            {"okta_username": "******", "multifactor_preference": "Google Authenticator", ...}
        :rtype: map[string, string]
        """
        debug_msg = 'Configuration:\n'
        for param in self.param_list:
            from_env = os.getenv(key=param.name, default=-1)
            if from_env != -1:
                # If defined in environment, use that first
                param.value = from_env
            elif param.name in config_section and config_section[param.name]:
                # If defined in the config file, make sure it's not a secret, otherwise use it
                if param.secret:
                    Common.dump_err(
                        message=
                        'Invalid configuration.  {} should never be defined in clokta.cfg.'
                        .format(param.name))
                    raise ValueError("Illegal configuration")
                if param.param_type == bool:
                    param.value = self.__validate_bool(
                        config_section[param.name], param.name)
                else:
                    param.value = config_section[param.name]
            elif param.secret:
                param.value = self.__read_from_keyring(param.name)

            if not param.value and param.required:
                # We need it.  Prompt for it.
                param.value = self.__prompt_for(param)
            debug_msg += '     {}={}'.format(
                param.name, param.value if not param.secret else 'xxxxxxxx')

        if Common.is_debug():
            Common.dump_out(message=debug_msg)
Beispiel #15
0
    def __wait_for_push_result(self, state_token, push_response):
        """
        A request was sent to Okta querying the status of a push.  Process the response, pull the session
        token from it, and store in self.session_token
        :param state_token: a token received from Okta identifying this authentication attempt session
        :type state_token: str
        :param push_response: the HTTP response from the HTTP request
        :type push_response: json
        :return: SUCCESS if response indicates SUCCESS.  INPUT_ERROR if timed out.
        """
        url = push_response['_links']['next']['href']
        headers = {
            'Accept': 'application/json',
            'Content-Type': 'application/json',
            'Cache-Control': '"no-cache'
        }
        payload = {'stateToken': state_token}

        wait_for = 60
        timeout = time.time() + wait_for
        response_data = None
        while True:
            Common.echo(message='.', new_line=False)
            response = requests.post(url,
                                     data=json.dumps(payload),
                                     headers=headers)
            if response.status_code == requests.codes.ok:  # pylint: disable=E1101
                response_data = response.json()
            else:
                response.raise_for_status()

            if 'sessionToken' in response_data or time.time() > timeout:
                break
            time.sleep(3)

        if response_data and 'sessionToken' in response_data:
            Common.echo(message='Session confirmed')
            self.session_token = response_data['sessionToken']
            return OktaInitiator.Result.SUCCESS
        else:
            msg = 'Timeout expired ({} seconds)'.format(wait_for)
            Common.dump_err(message=msg)
            return OktaInitiator.Result.INPUT_ERROR
Beispiel #16
0
 def __validate_bool(self, value, name="parameter"):
     """
     Verify a string is either True or False.
     If it is not, print an error.
     :param value: the string value
     :type value: str
     :param name: the name or context of the value to be used in an error message
     :type name: str
     :return: "True" if the string is some form of true,  "False" for any other case
     :rtype: str
     """
     if value.strip().lower() == "true":
         return "True"
     elif value.strip().lower() == "false":
         return "False"
     else:
         Common.dump_err(
             '{} configured with value "{}" when only True or False is valid.'
             .format(name, value))
         return "False"
Beispiel #17
0
 def __save_to_keyring(self, param_name, param_value):
     """
     Save a secret to the keychain.  Obfuscate the secret first.
     :param param_name: the name of the parameter
     :type param_name: str
     :param param_value: the secret to save
     :type param_value: str
     """
     if sys.version_info > (3, 0):
         try:
             system = CloktaConfiguration.KEYCHAIN_PATTERN.format(
                 param_name=param_name)
             user = self.get('okta_username')
             password = self.__obfuscate(param_value, user)
             keyring.set_password(system, user, password)
         except Exception as e:
             fail_msg = str(e)
             if fail_msg.find('Security Auth Failure') >= 0:
                 Common.dump_err(fail_msg)
                 Common.dump_err(
                     'WARNING: Denied access to password in keychain.  ' +
                     'If prompted by keychain, allow access.\n' +
                     'You may need to reboot your machine before keychain will prompt again.'
                 )
             else:
                 Common.dump_err(
                     'WARNING: Could not save password to keychain: {}'.
                     format(e))
Beispiel #18
0
 def __read_from_keyring(self, param_name):
     """
     Read a secret from the OS keychain
     :param param_name: the name of the parameter
     :type param_name: str
     :return: the value or None if not found or otherwise could not get value
     :rtype: str
     """
     param_value = None
     if sys.version_info > (3, 0):
         system = CloktaConfiguration.KEYCHAIN_PATTERN.format(
             param_name=param_name)
         user = self.get('okta_username')
         try:
             obfuscated = keyring.get_password(system, user)
             param_value = self.__deobfuscate(obfuscated, user)
         except Exception as e:
             fail_msg = str(e)
             if fail_msg.find('Security Auth Failure') >= 0:
                 Common.dump_err(
                     'WARNING: Denied access to password in keychain.  ' +
                     'If prompted by keychain, allow access.\n' +
                     'You may need to reboot your machine before keychain will prompt again.'
                 )
             else:
                 Common.dump_err(
                     'WARNING: Could not read password from keychain: {}'.
                     format(e))
     else:
         if not self.displayed_python2_warning:
             Common.dump_err(
                 "Cannot store password in keychain.  Upgrade to Python 3 to use keychain."
             )
             self.displayed_python2_warning = True
     return param_value
Beispiel #19
0
    def verify_preferred_factor(self):
        ''' Return the Okta MFA configuration for the matching, supported configuration '''
        preferred_factors = [
            opt for opt in self.option_factors
            if self.factor_preference == opt['prompt']
        ]
        if preferred_factors:
            if self.verbose:
                msg = 'Using preferred factor: {}'.format(self.factor_preference)
                Common.dump_verbose(message=msg)

            matching_okta_factor = [
                fact for fact in self.okta_factors
                if fact['provider'] == preferred_factors[0]['provider'] and
                fact['factorType'] == preferred_factors[0]['factor_type']
            ]

            return matching_okta_factor[0]

        else:
            msg = 'The MFA option \'{}\' in your configuration file is not available.\nAvailable options are {}'.format(
                self.factor_preference, [opt['prompt'] for opt in self.option_factors])
            Common.dump_err(message=msg, exit_code=3, verbose=self.verbose)
Beispiel #20
0
    def determine_okta_onetimepassword(self, factor, first_time):
        """
        Get the one time password, which may be in one password or
        may need to be prompted for
        :param factor: the mfa mechanism being used.  Holds a user friendly label for identifying which mechanism.
        :type factor: dict
        :param first_time: whether this is the first time this run determining one time password
            on subsequent attempts we don't try to get it with an otp secret because that
            obviously didn't work the first time
        :return: the Okta one time password
        :rtype: string
        """

        otp_value = None
        if self.get('okta_onetimepassword_secret'):
            if not first_time:
                Common.dump_err("OTP generator created incorrect OTP")
            else:
                try:
                    # noinspection PyUnresolvedReferences
                    import onetimepass as otp
                except ImportError:
                    msg = 'okta_onetimepassword_secret provided in config but "onetimepass" is not installed. ' + \
                          'run: pip install onetimepass'
                    Common.dump_err(message=msg)
                    raise ValueError("Illegal configuration")
                otp_value = otp.get_totp(
                    self.get('okta_onetimepassword_secret'))

        if not otp_value:
            otp_value = click.prompt(
                text='Enter your {} one time password'.format(
                    factor['clokta_id']),
                type=str,
                err=Common.to_std_error(),
                default='')
        return otp_value
Beispiel #21
0
    def __check_url(self, url):
        """
        Verify that the URL passed in is an Okta App URL to an AWS app.  Will raise an exception
        if url is invalid
        :return: the valid URL with any query parameters stripped off
        :rtype: str
        """
        # The URL should be of the form (though query parameter "fromHome" is optional)
        # https://<YOUR_COMPANY>.okta.com/home/amazon_aws/<UNIQUE_ID>/<APP_ID>?fromHome=true
        pattern = re.compile(
            'https://[^.]+.okta.com/home/amazon_aws/[^/]+/[0-9]+\\??')
        match = pattern.match(url)
        if match is None:
            Common.dump_err(
                "Invalid App URL.  URL usually of the form https://xxxxxxxx.okta.com/home/amazon_aws/...",
                6)
            raise ValueError("Invalid URL")
        else:
            # Strip off any query parameters
            paramIndex = url.find('?')
            if paramIndex >= 0:
                url = url[:paramIndex]

        return url
Beispiel #22
0
    def __auth_with_okta(self, configuration):
        """
        Authenticate with Okta.  If no further info is required, return SUCCESS with session_token set.
        If MFA response is required, return NEED_MFA with intermediate_state_token and factors set
        A rejection from Okta is interpretted as a bad password and INPUT_ERROR is returned.
        :param configuration: clokta configuration containing connection and user info
        :type configuration: CloktaConfiguration
        :return: SUCCESS, NEED_MFA or INPUT_ERROR
        :rtype: OktaInitiator.Result
        """
        self.session_token = None
        self.factors = []

        try:
            okta_response = self.__post_auth_request(configuration)
        except requests.exceptions.HTTPError as http_err:
            if Common.is_debug():
                Common.dump_out((
                    'Okta returned this credentials/password related error: {}\n'
                    +
                    'This could be a mistyped password or a misconfigured username '
                    + 'or URL.').format(http_err))
            return OktaInitiator.Result.INPUT_ERROR
        except Exception as err:
            Common.dump_err(
                'Unexpected error authenticating with Okta: {}'.format(err))
            raise

        if 'sessionToken' in okta_response and okta_response['sessionToken']:
            # MFA wasn't required.  We've got the token.
            self.session_token = okta_response['sessionToken']
            return OktaInitiator.Result.SUCCESS
        elif 'status' in okta_response and okta_response[
                'status'] == 'MFA_ENROLL':
            # handle case where MFA is required but no factors have been enabled
            Common.dump_err(
                'Please enroll in multi-factor authentication before using this tool'
            )
            raise ValueError("No MFA mechanisms configured")
        elif 'status' in okta_response and okta_response[
                'status'] == 'MFA_REQUIRED':
            self.factors = okta_response['_embedded']['factors']
            if not self.factors:
                # Another case where no factors have been enabled
                raise ValueError("No MFA mechanisms configured")
            self.intermediate_state_token = okta_response['stateToken']
            return OktaInitiator.Result.NEED_MFA
        else:
            Common.dump_err(
                'Unexpected response from Okta authentication request')
            raise RuntimeError("Unexpected response from Okta")
Beispiel #23
0
 def __submit_mfa_response(self, factor, otp):
     """
     post one time password to Okta
     :param factor: the MFA mechanism to use
     :type factor: dict
     :param otp: the one time password for MFA
     :type otp: str
     :return: SUCCESS if received a session token from Okta.  INPUT_ERROR if Okta reports incorrect
     one time password
     :rtype: OktaInitiator.Result
     """
     self.session_token = None
     try:
         mfa_response = self.__okta_mfa_verification(
             factor_dict=factor,
             state_token=self.intermediate_state_token,
             otp_value=otp)
         if 'sessionToken' in mfa_response:
             self.session_token = mfa_response['sessionToken']
             return OktaInitiator.Result.SUCCESS
         else:
             if 'status' not in mfa_response:
                 raise ValueError(
                     'Unexpected response from Okta.  ' +
                     'Received neither session token nor error status.')
             Common.dump_err(
                 'Okta failed to return session token and only returned this status: {}'
                 .format(mfa_response['status']))
             return OktaInitiator.Result.INPUT_ERROR
     except requests.exceptions.HTTPError as http_err:
         Common.dump_err(
             'Okta returned this MFA related error: {}'.format(http_err))
         return OktaInitiator.Result.INPUT_ERROR
     except Exception as err:
         msg = 'Unexpected error: {}'.format(err)
         Common.dump_err(message=msg)
         raise ValueError("Unexpected error with MFA")