def init_run_env(cls, value, values): # this enables us to load via name run_env and env_alias. run_env = values.get('run_env') if run_env: if isinstance(run_env, RunEnv): return run_env else: return RunEnv(env=values.get('run_env')) if not value: value = "unknown" return RunEnv(env=value)
def ots_svc(self, env: GlobalEnvironment, refresh: bool = False) -> OTSService: """ Returns a hydrated & properly configured One-time-secret service. This service will be configured for the appropriate "utility" account by the registry. """ new_env = env try: config_svc = self.config_svc(env=env, refresh=refresh) utility_account_id: Fig = config_svc.get_fig_with_cache(PS_FIGGY_UTILITY_ACCOUNT_ID).value log.debug(f"Got utility account id: {utility_account_id}") current_account_id: Fig = config_svc.get_fig_with_cache(PS_FIGGY_CURRENT_ACCOUNT_ID).value log.debug(f"Got current session alias: {current_account_id}") new_role = Role(role=FIGGY_DEFAULT_ROLE_NAME, full_name='figgy-default') regions = config_svc.get_fig_with_cache(PS_FIGGY_REGIONS) ots_region = json.loads(regions.value)[0] new_env = GlobalEnvironment(role=AssumableRole(account_id=utility_account_id, run_env=RunEnv(env="utility", account_id=utility_account_id), role=new_role, provider_name=env.role.provider_name), region=ots_region) except BaseException as e: raise InvalidFiggyConfigurationException(f"Unable to initialize one-time-secret service. Are you sure your " f"'utility_account_alias' was set to a valid value when you configured " f"Figgy Cloud?") from e config_svc = self.config_svc(env=new_env, refresh=refresh) ots_key: Fig = config_svc.get_fig_with_cache(PS_FIGGY_OTS_KEY_ID) return OTSService(self.__ssm(env=new_env, refresh=refresh), self.__kms(env=new_env, refresh=refresh), kms_id=ots_key.value)
def select_run_env(valid_envs: List[str]) -> RunEnv: input_env = prompt(f'Select a RunEnvironment: {valid_envs}: ', completer=WordCompleter(valid_envs)) Utils.stc_validate( input_env in valid_envs, f"{input_env} is not a valid Run Environment. Please select from: {valid_envs}" ) return RunEnv(env=input_env)
def select_default_account(valid_envs: List[str]) -> RunEnv: environment = None while environment not in valid_envs: environment = prompt( f'Please select a default account. All commands without the specified `--env` ' f'parameter will run against this account. \n Options are: {valid_envs}: \n -> ', completer=WordCompleter(valid_envs)) if environment not in valid_envs: print( f"{environment} is not a valid environment type. Please select from: {valid_envs}" ) return RunEnv(env=environment)
def _promote(self): repeat = True parameters: List[Dict] = [] while repeat: namespace = Input.input("Please input a namespace prefix to promote:" f" (i.e. {self.context.defaults.service_ns}/foo/): ", completer=self._config_completer) if not self._utils.is_valid_input(namespace, "namespace", notify=False): continue try: parameters: List[Dict] = self._source_ssm.get_all_parameters([namespace]) if not parameters and self._source_ssm.get_parameter(namespace): parameters, latest_version = self._source_ssm.get_parameter_details(namespace) parameters = list(parameters) if parameters: repeat = False else: self._out.warn("\nNo parameters found. Try again.\n") except ClientError as e: print(f"{self.c.fg_rd}ERROR: >> {e}{self.c.rs}") continue self._out.notify(f'\nFound [[{len(parameters)}]] parameter{"s" if len(parameters) > 1 else ""} to migrate.\n') assumable_roles = self.context.defaults.assumable_roles matching_roles = list(set([x for x in assumable_roles if x.role == self.config_context.role])) valid_envs = set([x.run_env.env for x in matching_roles]) valid_envs.remove(self.run_env.env) # Remove current env, we can't promote from dev -> dev next_env = Input.select(f'Please select the destination environment.', valid_options=list(valid_envs)) matching_role = [role for role in matching_roles if role.run_env == RunEnv(env=next_env)][0] env: GlobalEnvironment = GlobalEnvironment(role=matching_role, region=self.config_context.defaults.region) dest_ssm = SsmDao(self._session_mgr.get_session(env, prompt=False).client('ssm')) for param in parameters: if 'KeyId' in param: self._out.print(f"Skipping param: [[{param['Name']}]]. It is encrypted and cannot be migrated.") else: promote_it = Input.y_n_input(f"Would you like to promote: {param['Name']}?", default_yes=True) if promote_it: val = self._source_ssm.get_parameter(param['Name']) description = param.get('Description', "") dest_ssm.set_parameter(param['Name'], val, description, SSM_STRING) self._out.success(f"Successfully promoted [[{param['Name']}]] to [[{next_env}]].\r\n")
def unconfigured(): return CLIDefaults(role=Role(role="unconfigured"), colors_enabled=False, user=None, run_env=RunEnv(env="unconfigured"), provider=Provider.UNSELECTED, session_duration=DEFAULT_SESSION_DURATION, region="us-east-1", mfa_enabled=False, mfa_serial=None, provider_config=None, report_errors=False, auto_mfa=False, user_id=str(uuid.uuid4()), service_ns="/app", usage_tracking=False, extras={}, enabled_regions=["us-east-1"])
def from_profile(profile): return CLIDefaults(role=Role(role=profile), colors_enabled=Utils.not_windows(), user=profile, run_env=RunEnv(env=profile), provider=Provider.PROFILE, session_duration=DEFAULT_SESSION_DURATION, region="us-east-1", mfa_enabled=False, mfa_serial=None, provider_config=None, report_errors=False, auto_mfa=False, user_id=str(uuid.uuid4()), service_ns=os.environ.get(APP_NS_OVERRIDE) or "/app", usage_tracking=True, extras={}, enabled_regions=["us-east-1"])
def sandbox(user: str, role: str, colors: bool): return CLIDefaults(role=Role(role=role), colors_enabled=colors, user=user, run_env=RunEnv(env="unconfigured"), provider=Provider.AWS_BASTION, session_duration=SANDBOX_SESSION_DURATION, region=FIGGY_SANDBOX_REGION, mfa_enabled=False, mfa_serial=None, provider_config=BastionProviderConfig( profile_name=FIGGY_SANDBOX_PROFILE), report_errors=False, auto_mfa=False, user_id=str(uuid.uuid4()), service_ns="/app", usage_tracking=True, extras={DISABLE_KEYRING: True}, enabled_regions=["us-east-1"])
def __init__(self, args): """ Initializes global shared properties :param args: Arguments passed in from user, collected from ArgParse """ self._profile = None self._command_factory = None self._setup = None self._is_setup_command: bool = FiggyCLI.is_setup_command(args) self._utils = Utils(self.get_colors_enabled()) self._profile = Utils.attr_if_exists(profile, args) self._defaults: CLIDefaults = FiggySetup.stc_get_defaults(skip=self._is_setup_command, profile=self._profile) self._run_env = self._defaults.run_env role_override = Utils.attr_if_exists(role, args) self._role: Role = self.get_role(args.prompt, role_override=role_override, is_setup=self._is_setup_command) FiggyCLI.validate_environment(self._defaults) if not self._is_setup_command: if not hasattr(args, 'env') or args.env is None: print(f"{EMPTY_ENV_HELP_TEXT}{self._run_env.env}\n") else: Utils.stc_validate(args.env in self._defaults.valid_envs, f'{ENV_HELP_TEXT} {self._defaults.valid_envs}. Provided: {args.env}') self._run_env = RunEnv(env=args.env) self._utils.validate(Utils.attr_exists(configure, args) or Utils.attr_exists(command, args), f"No command found. Proper format is `{CLI_NAME} <resource> <command> --option(s)`") self._assumable_role = self.find_assumable_role(self._run_env, self._role, skip=self._is_setup_command, profile=self._profile) command_name = Utils.attr_if_exists(command, args) resource_name = Utils.attr_if_exists(resource, args) found_command: CliCommand = Utils.find_command(str(command_name)) found_resource: CliCommand = Utils.find_resource(str(resource_name)) self._context: FiggyContext = FiggyContext(self.get_colors_enabled(), found_resource, found_command, self._run_env, self._assumable_role, args)
def __lookup_roles(self) -> List[AssumableRole]: assertion = self.get_saml_assertion(prompt=False) root = ET.fromstring(assertion) prefix_map = {"saml2": "urn:oasis:names:tc:SAML:2.0:assertion"} role_attribute = root.find( ".//saml2:Attribute[@Name='https://aws.amazon.com/SAML/Attributes/Role']", prefix_map) # SAML arns should look something like this: # arn:aws:iam::106481321259:saml-provider/okta,arn:aws:iam::106481321259:role/figgy-dev-data # One exception is the `figgy-default` role. pattern = r'^arn:aws:iam::([0-9]+):saml-provider/(\w+),arn:aws:iam::.*role/(\w+-(\w+)-(\w+))' assumable_roles: List[AssumableRole] = [] for value in role_attribute.findall('.//saml2:AttributeValue', prefix_map): if FIGGY_DEFAULT_ROLE_NAME not in value: result = re.search(pattern, value.text) unparsable_msg = f'{value.text} is of an invalid pattern, it must match: {pattern} for figgy to ' \ f'dynamically map account_id -> run_env -> role for OKTA users. If this is not a figgy role, ' \ f'ignore this message.' if not result: Utils.stc_warn(unparsable_msg) continue result.groups() account_id, provider_name, role_name, run_env, role = result.groups( ) if not account_id or not run_env or not role_name or not role: Utils.stc_error_exit(unparsable_msg) else: assumable_roles.append( AssumableRole(account_id=account_id, role=Role(role=role, full_name=role_name), run_env=RunEnv(env=run_env), provider_name=provider_name, profile=None)) return assumable_roles
def get_assumable_roles(self): if self.is_role_session(): user_roles = [self._defaults.role.role] else: ROLE_PATH = f'/figgy/users/{self.__get_iam_user()}/roles' user_roles = self.__get_ssm().get_parameter(ROLE_PATH) self._utils.stc_validate( user_roles is not None and user_roles != "[]", "Something is wrong with your user's configuration with Figgy. " "Unable to find any eligible roles for your user. Please contact your" " administrator.") user_roles = json.loads(user_roles) environments = self.__get_ssm().get_all_parameters( [PS_FIGGY_ACCOUNTS_PREFIX], option='OneLevel') names: List[str] = [env.get('Name') for env in environments] parameters = self.__get_ssm().get_parameter_values(names) assumable_roles: List[AssumableRole] = [] for param in parameters: env_name = param.get('Name').split('/')[-1] account_id = param.get('Value') for role in user_roles: assumable_roles.append( AssumableRole( run_env=RunEnv(env=env_name, account_id=account_id), role=Role( role=role, full_name= f'{FIGGY_ROLE_NAME_PREFIX}{env_name}-{role}'), account_id=account_id, provider_name=Provider.AWS_BASTION.value, profile=None)) return assumable_roles
def from_profile(profile: str): return AssumableRole(account_id=1234567899, run_env=RunEnv(env=profile), profile=profile, role=Role(role=profile), provider_name=Provider.PROFILE.value)
def login_sandbox(self): """ If user provides --role flag, skip role & env selection for a smoother user experience. """ EnvironmentValidator(self._defaults).validate_environment_variables() Utils.wipe_vaults() or Utils.wipe_defaults( ) or Utils.wipe_config_cache() self._out.print( f"{self.c.fg_bl}Logging you into the Figgy Sandbox environment.{self.c.rs}" ) user = Input.input("Please input a user name: ", min_length=2) colors = Input.select_enable_colors() # Prompt user for role if --role not provided if commands.role not in self.context.options: role = Input.select("\n\nPlease select a role to impersonate: ", valid_options=SANDBOX_ROLES) else: role = self.context.role.role self._utils.validate( role in SANDBOX_ROLES, f"Provided role: >>>`{role}`<<< is not a valid sandbox role." f" Please choose from {SANDBOX_ROLES}") params = {'role': role, 'user': user} result = requests.get(GET_SANDBOX_CREDS_URL, params=params) if result.status_code != 200: self._utils.error_exit( "Unable to get temporary credentials from the Figgy sandbox. If this problem " f"persists please notify us on our GITHUB: {FIGGY_GITHUB}") data = result.json() response = SandboxLoginResponse(**data) self._aws_cfg.write_credentials( access_key=response.AWS_ACCESS_KEY_ID, secret_key=response.AWS_SECRET_ACCESS_KEY, token=response.AWS_SESSION_TOKEN, region=FIGGY_SANDBOX_REGION, profile_name=FIGGY_SANDBOX_PROFILE) defaults = CLIDefaults.sandbox(user=user, role=role, colors=colors) self._setup.save_defaults(defaults) run_env = RunEnv( env='dev', account_id=SANDBOX_DEV_ACCOUNT_ID) if self.context.role else None config_mgr = ConfigManager.figgy() config_mgr.set(Config.Section.Bastion.PROFILE, FIGGY_SANDBOX_PROFILE) defaults = self._setup.configure_extras(defaults) defaults = self._setup.configure_roles(current_defaults=defaults, role=Role(role=role), run_env=run_env) defaults = self._setup.configure_figgy_defaults(defaults) self._setup.save_defaults(defaults) self._out.success( f"\nLogin successful. Your sandbox session will last for [[1 hour]]." ) self._out.print( f"\nIf your session expires, you may rerun `{CLI_NAME} login sandbox` to get another sandbox session. " f"\nAll previous figgy sessions have been disabled, you'll need to run {CLI_NAME} " f"--configure to leave the sandbox.")