Beispiel #1
0
def _repo_role(
    account_number,
    role_name,
    dynamo_table,
    config,
    hooks,
    commit=False,
    scheduled=False,
):
    """
    Calculate what repoing can be done for a role and then actually do it if commit is set
      1) Check that a role exists, it isn't being disqualified by a filter, and that is has fresh AA data
      2) Get the role's current permissions, repoable permissions, and the new policy if it will change
      3) Make the changes if commit is set
    Args:
        account_number (string)
        role_name (string)
        commit (bool)

    Returns:
        None
    """
    errors = []

    role_id = find_role_in_cache(dynamo_table, account_number, role_name)
    # only load partial data that we need to determine if we should keep going
    role_data = get_role_data(
        dynamo_table,
        role_id,
        fields=["DisqualifiedBy", "AAData", "RepoablePermissions", "RoleName"],
    )
    if not role_data:
        LOGGER.warn("Could not find role with name {}".format(role_name))
        return
    else:
        role = Role(role_data)

    continuing = True

    if len(role.disqualified_by) > 0:
        LOGGER.info(
            "Cannot repo role {} in account {} because it is being disqualified by: {}"
            .format(role_name, account_number, role.disqualified_by))
        continuing = False

    if not role.aa_data:
        LOGGER.warning("ARN not found in Access Advisor: {}".format(role.arn))
        continuing = False

    if not role.repoable_permissions:
        LOGGER.info("No permissions to repo for role {} in account {}".format(
            role_name, account_number))
        continuing = False

    # if we've gotten to this point, load the rest of the role
    role = Role(get_role_data(dynamo_table, role_id))

    old_aa_data_services = []
    for aa_service in role.aa_data:
        if datetime.datetime.strptime(
                aa_service["lastUpdated"], "%a, %d %b %Y %H:%M:%S %Z"
        ) < datetime.datetime.now() - datetime.timedelta(
                days=config["repo_requirements"]["oldest_aa_data_days"]):
            old_aa_data_services.append(aa_service["serviceName"])

    if old_aa_data_services:
        LOGGER.error(
            "AAData older than threshold for these services: {} (role: {}, account {})"
            .format(old_aa_data_services, role_name, account_number),
            exc_info=True,
        )
        continuing = False

    total_permissions, eligible_permissions = roledata._get_role_permissions(
        role)
    repoable_permissions = roledata._get_repoable_permissions(
        account_number,
        role.role_name,
        eligible_permissions,
        role.aa_data,
        role.no_repo_permissions,
        config["filter_config"]["AgeFilter"]["minimum_age"],
        hooks,
    )

    # if this is a scheduled repo we need to filter out permissions that weren't previously scheduled
    if scheduled:
        repoable_permissions = roledata._filter_scheduled_repoable_perms(
            repoable_permissions, role.scheduled_perms)

    repoed_policies, deleted_policy_names = roledata._get_repoed_policy(
        role.policies[-1]["Policy"], repoable_permissions)

    if inline_policies_size_exceeds_maximum(repoed_policies):
        error = (
            "Policies would exceed the AWS size limit after repo for role: {} in account {}.  "
            "Please manually minify.".format(role_name, account_number))
        LOGGER.error(error)
        errors.append(error)
        continuing = False

    # if we aren't repoing for some reason, unschedule the role
    if not continuing:
        set_role_data(dynamo_table, role.role_id, {
            "RepoScheduled": 0,
            "ScheduledPerms": []
        })
        return

    if not commit:
        log_deleted_and_repoed_policies(deleted_policy_names, repoed_policies,
                                        role_name, account_number)
        return

    conn = config["connection_iam"]
    conn["account_number"] = account_number

    for name in deleted_policy_names:
        error = delete_policy(name, role, account_number, conn)
        if error:
            LOGGER.error(error)
            errors.append(error)

    if repoed_policies:
        error = replace_policies(repoed_policies, role, account_number, conn)
        if error:
            LOGGER.error(error)
            errors.append(error)

    current_policies = get_role_inline_policies(role.as_dict(), **conn) or {}
    roledata.add_new_policy_version(dynamo_table, role, current_policies,
                                    "Repo")

    # regardless of whether we're successful we want to unschedule the repo
    set_role_data(dynamo_table, role.role_id, {
        "RepoScheduled": 0,
        "ScheduledPerms": []
    })

    repokid.hooks.call_hooks(hooks, "AFTER_REPO", {
        "role": role,
        "errors": errors
    })

    if not errors:
        # repos will stay scheduled until they are successful
        set_role_data(
            dynamo_table,
            role.role_id,
            {"Repoed": datetime.datetime.utcnow().isoformat()},
        )
        update_repoed_description(role.role_name, **conn)
        partial_update_role_data(
            role,
            dynamo_table,
            account_number,
            config,
            conn,
            hooks,
            source="Repo",
            add_no_repo=False,
        )
        LOGGER.info("Successfully repoed role: {} in account {}".format(
            role.role_name, account_number))
    return errors
Beispiel #2
0
def remove_permissions_from_role(
    account_number,
    permissions,
    role,
    role_id,
    dynamo_table,
    config,
    hooks,
    commit=False,
):
    """Remove the list of permissions from the provided role.

    Args:
        account_number (string)
        permissions (list<string>)
        role (Role object)
        role_id (string)
        commit (bool)

    Returns:
        None
    """
    repoed_policies, deleted_policy_names = roledata._get_repoed_policy(
        role.policies[-1]["Policy"], permissions)

    if inline_policies_size_exceeds_maximum(repoed_policies):
        LOGGER.error(
            "Policies would exceed the AWS size limit after repo for role: {} in account {}.  "
            "Please manually minify.".format(role.role_name, account_number))
        return

    if not commit:
        log_deleted_and_repoed_policies(deleted_policy_names, repoed_policies,
                                        role.role_name, account_number)
        return

    conn = config["connection_iam"]
    conn["account_number"] = account_number

    for name in deleted_policy_names:
        error = delete_policy(name, role, account_number, conn)
        if error:
            LOGGER.error(error)

    if repoed_policies:
        error = replace_policies(repoed_policies, role, account_number, conn)
        if error:
            LOGGER.error(error)

    current_policies = get_role_inline_policies(role.as_dict(), **conn) or {}
    roledata.add_new_policy_version(dynamo_table, role, current_policies,
                                    "Repo")

    set_role_data(dynamo_table, role.role_id,
                  {"Repoed": datetime.datetime.utcnow().isoformat()})
    update_repoed_description(role.role_name, **conn)
    partial_update_role_data(
        role,
        dynamo_table,
        account_number,
        config,
        conn,
        hooks,
        source="ManualPermissionRepo",
        add_no_repo=False,
    )
    LOGGER.info(
        "Successfully removed {permissions} from role: {role} in account {account_number}"
        .format(permissions=permissions,
                role=role.role_name,
                account_number=account_number))
Beispiel #3
0
    def repo(self,
             hooks: RepokidHooks,
             commit: bool = False,
             scheduled: bool = False) -> List[str]:
        errors: List[str] = []

        eligible, reason = self.is_eligible_for_repo()
        if not eligible:
            errors.append(
                f"Role {self.role_name} not eligible for repo: {reason}")
            return errors

        self.calculate_repo_scores(
            self.config["filter_config"]["AgeFilter"]["minimum_age"],
            hooks  # type: ignore
        )
        try:
            repoed_policies, deleted_policy_names = self.get_repoed_policy(
                scheduled=scheduled)
        except MissingRepoableServices as e:
            errors.append(f"Role {self.role_name} cannot be repoed: {e}")
            return errors

        if inline_policies_size_exceeds_maximum(repoed_policies):
            error = (
                "Policies would exceed the AWS size limit after repo for role: {} in account {}.  "
                "Please manually minify.".format(self.role_name, self.account))
            logger.error(error)
            errors.append(error)
            self.repo_scheduled = 0
            self.scheduled_perms = []
            self.store(["repo_scheduled", "scheduled_perms"])
            return errors

        if not commit:
            log_deleted_and_repoed_policies(deleted_policy_names,
                                            repoed_policies, self.role_name,
                                            self.account)
            return errors

        conn = self.config["connection_iam"]  # type: ignore
        conn["account_number"] = self.account

        for name in deleted_policy_names:
            try:
                delete_policy(name, self.role_name, self.account, conn)
            except IAMError as e:
                logger.error(e)
                errors.append(str(e))

        if repoed_policies:
            try:
                replace_policies(repoed_policies, self.role_name, self.account,
                                 conn)
            except IAMError as e:
                logger.error(e)
                errors.append(str(e))

        current_policies = (get_role_inline_policies(self.dict(by_alias=True),
                                                     **conn) or {})
        self.add_policy_version(current_policies, source="Repo")

        # regardless of whether we're successful we want to unschedule the repo
        self.repo_scheduled = 0
        self.scheduled_perms = []

        call_hooks(hooks, "AFTER_REPO", {"role": self, "errors": errors})

        if not errors:
            # repos will stay scheduled until they are successful
            self.repoed = datetime.datetime.now(
                tz=datetime.timezone.utc).isoformat()
            update_repoed_description(self.role_name, conn)
            logger.info("Successfully repoed role: {} in account {}".format(
                self.role_name, self.account))
        try:
            self.store()
        except RoleStoreError:
            logger.exception("failed to store role after repo", exc_info=True)
        return errors
Beispiel #4
0
    def remove_permissions(self,
                           permissions: List[str],
                           hooks: RepokidHooks,
                           commit: bool = False) -> None:
        """Remove the list of permissions from the provided role.

        Args:
            account_number (string)
            permissions (list<string>)
            role (Role object)
            role_id (string)
            commit (bool)

        Returns:
            None
        """
        (
            repoed_policies,
            deleted_policy_names,
        ) = get_repoed_policy(self.policies[-1]["Policy"], set(permissions))

        if inline_policies_size_exceeds_maximum(repoed_policies):
            logger.error(
                "Policies would exceed the AWS size limit after repo for role: {} in account {}.  "
                "Please manually minify.".format(self.role_name, self.account))
            return

        if not commit:
            log_deleted_and_repoed_policies(deleted_policy_names,
                                            repoed_policies, self.role_name,
                                            self.account)
            return

        conn = self.config["connection_iam"]  # type: ignore
        conn["account_number"] = self.account

        for name in deleted_policy_names:
            try:
                delete_policy(name, self.role_name, self.account, conn)
            except IAMError as e:
                logger.error(e)

        if repoed_policies:
            try:
                replace_policies(repoed_policies, self.role_name, self.account,
                                 conn)
            except IAMError as e:
                logger.error(e)

        current_policies = get_role_inline_policies(self.dict(), **conn) or {}
        self.add_policy_version(current_policies, "Repo")

        self.repoed = datetime.datetime.now(
            tz=datetime.timezone.utc).isoformat()
        update_repoed_description(self.role_name, conn)
        self.gather_role_data(
            hooks,
            current_policies=current_policies,
            source="ManualPermissionRepo",
            add_no_repo=False,
        )
        logger.info(
            "Successfully removed {permissions} from role: {role} in account {account_number}"
            .format(
                permissions=permissions,
                role=self.role_name,
                account_number=self.account,
            ))
Beispiel #5
0
def _deal_with_policies(role, account_number, config, hooks, scheduled,
                        role_name, dynamo_table, commit, continuing):
    errors = []
    total_permissions, eligible_permissions = roledata._get_role_permissions(
        role)
    repoable_permissions = roledata._get_repoable_permissions(
        account_number,
        role.role_name,
        eligible_permissions,
        role.aa_data,
        role.no_repo_permissions,
        config["filter_config"]["AgeFilter"]["minimum_age"],
        hooks,
    )
    # if this is a scheduled repo we need to filter out permissions that weren't previously scheduled
    if scheduled:
        repoable_permissions = roledata._filter_scheduled_repoable_perms(
            repoable_permissions, role.scheduled_perms)

    repoed_policies, deleted_policy_names = roledata._get_repoed_policy(
        role.policies[-1]["Policy"], repoable_permissions)

    if inline_policies_size_exceeds_maximum(repoed_policies):
        error = (
            "Policies would exceed the AWS size limit after repo for role: {} in account {}.  "
            "Please manually minify.".format(role_name, account_number))
        LOGGER.error(error)
        errors.append(error)
        continuing = False

    # if we aren't repoing for some reason, unschedule the role
    if not continuing:
        set_role_data(dynamo_table, role.role_id, {
            "RepoScheduled": 0,
            "ScheduledPerms": []
        })
        return

    if not commit:
        log_deleted_and_repoed_policies(deleted_policy_names, repoed_policies,
                                        role_name, account_number)
        return

    conn = config["connection_iam"]
    conn["account_number"] = account_number

    for name in deleted_policy_names:
        error = delete_policy(name, role, account_number, conn)
        if error:
            LOGGER.error(error)
            errors.append(error)

    if repoed_policies:
        error = replace_policies(repoed_policies, role, account_number, conn)
        if error:
            LOGGER.error(error)
            errors.append(error)

    current_policies = get_role_inline_policies(role.as_dict(), **conn) or {}
    roledata.add_new_policy_version(dynamo_table, role, current_policies,
                                    "Repo")

    # regardless of whether we're successful we want to unschedule the repo
    set_role_data(dynamo_table, role.role_id, {
        "RepoScheduled": 0,
        "ScheduledPerms": []
    })

    repokid.hooks.call_hooks(hooks, "AFTER_REPO", {
        "role": role,
        "errors": errors
    })

    if not errors:
        # repos will stay scheduled until they are successful
        set_role_data(
            dynamo_table,
            role.role_id,
            {"Repoed": datetime.datetime.utcnow().isoformat()},
        )
        update_repoed_description(role.role_name, **conn)
        partial_update_role_data(
            role,
            dynamo_table,
            account_number,
            config,
            conn,
            hooks,
            source="Repo",
            add_no_repo=False,
        )
        LOGGER.info("Successfully repoed role: {} in account {}".format(
            role.role_name, account_number))
    return errors
Beispiel #6
0
def _repo_role(
    account_number: str,
    role_name: str,
    config: RepokidConfig,
    hooks: RepokidHooks,
    commit: bool = False,
    scheduled: bool = False,
) -> List[str]:
    """
    Calculate what repoing can be done for a role and then actually do it if commit is set
      1) Check that a role exists, it isn't being disqualified by a filter, and that is has fresh AA data
      2) Get the role's current permissions, repoable permissions, and the new policy if it will change
      3) Make the changes if commit is set
    Args:
        account_number (string)
        role_name (string)
        commit (bool)

    Returns:
        None
    """
    errors: List[str] = []

    role_id = find_role_in_cache(role_name, account_number)
    # only load partial data that we need to determine if we should keep going
    role = Role(role_id=role_id)
    role.fetch()

    continuing = True

    eligible, reason = role.is_eligible_for_repo()
    if not eligible:
        errors.append(f"Role {role_name} not eligible for repo: {reason}")
        return errors

    role.calculate_repo_scores(
        config["filter_config"]["AgeFilter"]["minimum_age"], hooks)
    repoed_policies, deleted_policy_names = role.get_repoed_policy(
        scheduled=scheduled)

    if inline_policies_size_exceeds_maximum(repoed_policies):
        error = (
            "Policies would exceed the AWS size limit after repo for role: {} in account {}.  "
            "Please manually minify.".format(role_name, account_number))
        LOGGER.error(error)
        errors.append(error)
        continuing = False

    # if we aren't repoing for some reason, unschedule the role
    if not continuing:
        role.repo_scheduled = 0
        role.scheduled_perms = []
        role.store(["repo_scheduled", "scheduled_perms"])
        return errors

    if not commit:
        log_deleted_and_repoed_policies(deleted_policy_names, repoed_policies,
                                        role_name, account_number)
        return errors

    conn = config["connection_iam"]
    conn["account_number"] = account_number

    for name in deleted_policy_names:
        try:
            delete_policy(name, role, account_number, conn)
        except IAMError as e:
            LOGGER.error(e)
            errors.append(str(e))

    if repoed_policies:
        try:
            replace_policies(repoed_policies, role, account_number, conn)
        except IAMError as e:
            LOGGER.error(e)
            errors.append(str(e))

    current_policies = get_role_inline_policies(role.dict(by_alias=True), **
                                                conn) or {}
    role.add_policy_version(current_policies, source="Repo")

    # regardless of whether we're successful we want to unschedule the repo
    role.repo_scheduled = 0
    role.scheduled_perms = []

    repokid.hooks.call_hooks(hooks, "AFTER_REPO", {
        "role": role,
        "errors": errors
    })

    if not errors:
        # repos will stay scheduled until they are successful
        role.repoed = datetime.datetime.now(
            tz=datetime.timezone.utc).isoformat()
        update_repoed_description(role.role_name, conn)
        role.gather_role_data(current_policies,
                              hooks,
                              source="Repo",
                              add_no_repo=False)
        LOGGER.info("Successfully repoed role: {} in account {}".format(
            role.role_name, account_number))
    role.store()
    return errors