def __init__(self, child, extra_args=""): self.c = Utils.default_colors() self.extra_args = extra_args if child: c = Utils.default_colors() print(f"{c.fg_yl}Testing command: {child.args}{c.rs}") self._child = child self._child.logfile = sys.stdout self._child.delaybeforesend = .5
def __init__(self, context: FiggyContext): self._cache_mgr = CacheManager(file_override=DEFAULTS_FILE_CACHE_PATH) self._config_mgr, self.c = ConfigManager.figgy(), Utils.default_colors() self._session_mgr = None self._session_provider = None self._secrets_mgr = SecretsManager() self._figgy_context = context
def __init__(self, command, extra_args=""): self.c = Utils.default_colors() self.command = command self.extra_args = extra_args self._child = self.spawn(command) print(f"{self.c.fg_yl}Executing action: {self._child.args}{self.c.rs}") self._child.logfile = sys.stdout self._child.delaybeforesend = .5
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 configure(mfa_enabled: bool = False) -> "GoogleProviderConfig": config, c = ConfigManager(CONFIG_OVERRIDE_FILE_PATH), Utils.default_colors() idp_id = config.get_property(Config.Section.Google.IDP_ID) sp_id = config.get_property(Config.Section.Google.SP_ID) if idp_id and idp_id != FAKE_GOOGLE_IDP_ID: idp_id = config.get_or_prompt(Config.Section.Google.IDP_ID, GoogleProviderConfig.get_idp_id) else: idp_id = config.get_or_prompt(Config.Section.Google.IDP_ID, GoogleProviderConfig.get_idp_id, force_prompt=True) if sp_id and sp_id != FAKE_GOOGLE_SP_ID: sp_id = config.get_or_prompt(Config.Section.Google.SP_ID, GoogleProviderConfig.get_sp_id) else: sp_id = config.get_or_prompt(Config.Section.Google.SP_ID, GoogleProviderConfig.get_sp_id, force_prompt=True) return GoogleProviderConfig(idp_id=idp_id, sp_id=sp_id)
def configure(mfa_enabled: bool = False) -> "OktaProviderConfig": config, c = ConfigManager(CONFIG_OVERRIDE_FILE_PATH), Utils.default_colors() app_link = config.get_property(Config.Section.Google.IDP_ID) if app_link and app_link != FAKE_OKTA_APP_LINK: app_link = config.get_or_prompt(Config.Section.Okta.APP_LINK, OktaProviderConfig.get_app_link, desc=OKTA_APP_LINK_DESC) else: app_link = config.get_or_prompt(Config.Section.Okta.APP_LINK, OktaProviderConfig.get_app_link, force_prompt=True, desc=OKTA_APP_LINK_DESC) if mfa_enabled: factor_type = config.get_or_prompt(Config.Section.Okta.FACTOR_TYPE, OktaProviderConfig.get_factor_type, desc=OKTA_MFA_TYPE_DESC) else: factor_type = None return OktaProviderConfig(app_link=app_link, factor_type=factor_type)
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 configure(mfa_enabled: bool = False) -> "BastionProviderConfig": config, c = ConfigManager(CONFIG_OVERRIDE_FILE_PATH), Utils.default_colors() profile = config.get_or_prompt(Config.Section.Bastion.PROFILE, BastionProviderConfig.get_profile) return BastionProviderConfig(profile_name=profile)
from figcli.test.cli.dev.export import DevExport from figcli.test.cli.dev.get import DevGet from figcli.test.cli.dev.list import DevList from figcli.test.cli.dev.put import DevPut from figcli.test.cli.dev.restore import DevRestore from figcli.test.cli.dev.sync import DevSync from figcli.test.cli.dev.login import DevLogin from figcli.test.cli.sso.google.configure import ConfigureGoogle from figcli.utils.utils import Utils from figcli.test.cli.sso.okta.configure import ConfigureOkta CACHE_DIR = f'{HOME}/.figgy/cache' VAULT_DIR = f'{HOME}/.figgy/lockbox' CONFIG_FILE = f'{HOME}/.figgy/config' KEYRING_FILE = f'{HOME}/.local/share/python_keyring/keyring_pass.cfg' c = Utils.default_colors() AUTH_TYPES = ['google', 'okta', 'bastion', 'profile'] # FYI I know tests are a bit UGLY, but I'm ok with it, it's just tests! :) ## JORDAN: If you get EOF exceptions like this: ## pexpect.exceptions.EOF: End Of File (EOF). Empty string style platform. ## It means you created a TestObj and are calling `.expect()` on it after the child ## process has already exited. For instance, creating a single DevGet() and calling .get() numerous times. ## ## ALSO: You must always have an `child.expect` looking for the _last_ line of output, otherwise pexpect will kill ## the child process even if the child process is still finishing some stuff in the background. ## ## ## If you forget "encoding='utf-8' on a spawn() call logs will break! Use TestUtils going forward.
def __init__(self, colors_enabled: bool): self._utils = Utils(colors_enabled=colors_enabled) self.c = Utils.default_colors(enabled=colors_enabled) self.current_version: FiggyVersionDetails = VersionTracker.get_version()
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