Exemplo n.º 1
0
    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)
Exemplo n.º 2
0
    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)
Exemplo n.º 3
0
    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)
Exemplo n.º 4
0
    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)
Exemplo n.º 5
0
    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")
Exemplo n.º 6
0
 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"])
Exemplo n.º 7
0
 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"])
Exemplo n.º 8
0
 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"])
Exemplo n.º 9
0
    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)
Exemplo n.º 10
0
    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
Exemplo n.º 11
0
    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
Exemplo n.º 12
0
 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)
Exemplo n.º 13
0
    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.")