Example #1
0
def _get_users_without_access(db, auth_ids, user_emails, check_linking):
    """
    Build list of users without access to projects identified by auth_ids

    Args:
        db (str): database instance
        auth_ids (list(str)): list of project auth_ids to check access against
        user_emails (list(str)): list of emails to check access for
        check_linking (bool): flag to check for linked google email

    Returns:
        dict{str : (list(str))} : dictionary where keys are user emails,
        and values are list of project_ids they do not have access to

    """

    no_access = {}

    for user_email in user_emails:

        user = get_user_by_email(user_email, db) or get_user_by_linked_email(
            user_email, db)

        logger.info("Checking access for {}.".format(user.email))

        if not user:
            logger.info("Email ({}) does not exist in fence database.".format(
                user_email))
            continue

        if check_linking:
            link_email = get_linked_google_account_email(user.id, db)
            if not link_email:
                logger.info(
                    "User ({}) does not have a linked google account.".format(
                        user_email))
                continue

        no_access_auth_ids = []
        for auth_id in auth_ids:
            project = get_project_from_auth_id(auth_id, db)
            if project:
                if not user_has_access_to_project(user, project.id, db):
                    logger.info(
                        "User ({}) does NOT have access to project (auth_id: {})"
                        .format(user_email, auth_id))
                    # add to list to send email
                    no_access_auth_ids.append(auth_id)
                else:
                    logger.info(
                        "User ({}) has access to project (auth_id: {})".format(
                            user_email, auth_id))
            else:
                logger.warning(
                    "Project (auth_id: {}) does not exist.".format(auth_id))

        if no_access_auth_ids:
            no_access[user_email] = no_access_auth_ids

    return no_access
Example #2
0
    def check_validity(self, early_return=True, db=None):
        """
        Determine whether or not project is valid for registration. If
        early_return is False, this object will store information about the
        failure.

        Args:
            early_return (bool, optional): Description
        """

        self.google_cloud_manager.open()

        logger.debug(
            "Google Project with id: {}, "
            "new service account requested: {}, project access requested: {}, "
            "user requesting: {}".format(
                self.google_project_id,
                self.new_service_account,
                self.new_service_account_access,
                self.user_id,
            )
        )

        logger.debug(
            "Attempting to get project number "
            "for project id {}".format(self.google_project_id)
        )
        google_project_number = get_google_project_number(
            self.google_project_id, self.google_cloud_manager
        )
        has_access = bool(google_project_number)

        self.set("monitor_has_access", has_access)
        # always early return if we can't access the project
        if not has_access:
            logger.warning(
                "INVALID Fence's Monitoring service account does "
                "NOT have access in project id {}. Monitor needs access to continue "
                "checking project validity. Exiting early and determining invalid.".format(
                    self.user_id, self.google_project_id
                )
            )
            return

        logger.debug(
            "Retrieving project membership "
            "for project id {}".format(self.google_project_id)
        )
        membership = get_google_project_membership(
            self.google_project_id, self.google_cloud_manager
        )
        logger.debug(
            "Project Members: {}".format(
                str(
                    [
                        getattr(member, "email_id", "unknown_member_email")
                        for member in membership
                    ]
                )
            )
        )

        if self.user_id is not None:
            logger.debug(
                "Checking that user requesting, {}, is part of "
                "the Google Project with id {}".format(
                    self.user_id, self.google_project_id
                )
            )
            user_has_access = is_user_member_of_google_project(
                self.user_id, self.google_cloud_manager, membership=membership, db=db
            )
            self.set("user_has_access", user_has_access)
            if not user_has_access:
                # always early return if user isn't a member on the project
                logger.warning(
                    "INVALID User {} "
                    "for project id {}. User is not a member of the project so does not "
                    "have permission on the project.".format(
                        self.user_id, self.google_project_id
                    )
                )
                return

        logger.debug(
            "Retrieving Parent Organization "
            "for project id {} to make sure it's valid".format(self.google_project_id)
        )
        parent_org = get_google_project_parent_org(self.google_cloud_manager)
        valid_parent_org = True

        if parent_org:
            valid_parent_org = is_org_whitelisted(parent_org)

        self.set("valid_parent_org", valid_parent_org)

        if not valid_parent_org:
            logger.warning(
                "INVALID Parent Organization {} "
                "for project id {}. No parent org is allowed unless it's explicitly "
                "whitelisted in cfg.".format(parent_org, self.google_project_id)
            )
            if early_return:
                return

        logger.debug(
            "Determining if other users and service accounts on "
            "project id {} are valid.".format(self.google_project_id)
        )
        user_members = None
        service_account_members = []
        try:
            user_members, service_account_members = get_google_project_valid_users_and_service_accounts(
                self.google_project_id, self.google_cloud_manager, membership=membership
            )
            self.set("valid_member_types", True)
        except Exception:
            self.set("valid_member_types", False)
            logger.warning(
                "INVALID users and/or service accounts (SAs) on "
                "project id {}.".format(self.google_project_id)
            )
            if early_return:
                return

        logger.debug(
            "Determining if valid users exist in fence.".format(self.google_project_id)
        )
        # if we have valid members, we can check if they exist in fence
        users_in_project = None
        if user_members is not None:
            try:
                users_in_project = get_users_from_google_members(user_members, db=db)
                self.set("members_exist_in_fence", True)
            except Exception as e:
                self.set("members_exist_in_fence", False)
                logger.warning(
                    "INVALID user(s) do not exist in fence and thus, "
                    "we cannot determine their authZ info: {}.".format(e.message)
                )
                if early_return:
                    return

        # use a generic validityinfo object to hold all the service accounts
        # validity. then check all the service accounts. Top level will be
        # invalid if any service accounts are invalid
        new_service_account_validity = ValidityInfo()
        if self.new_service_account:
            service_account_validity_info = GoogleServiceAccountValidity(
                self.new_service_account,
                self.google_project_id,
                google_project_number=google_project_number,
                google_cloud_manager=self.google_cloud_manager,
            )

            service_account_id = str(self.new_service_account)

            logger.debug(
                "Google Project with id: {} and number: {}. "
                "Beginning validation on service account for registration {}".format(
                    self.google_project_id, google_project_number, service_account_id
                )
            )

            logger.debug(
                "Determining if the service account {} is google-managed.".format(
                    service_account_id
                )
            )
            # we do NOT need to check the service account type and external access
            # for google-managed accounts.
            if is_google_managed_service_account(service_account_id):
                logger.debug(
                    "GCP SA Validity -Service account {} IS google-managed. Therefore, "
                    "we do NOT need to check the SA Type or if it has external access.".format(
                        service_account_id
                    )
                )
                service_account_validity_info.check_validity(
                    early_return=early_return,
                    check_type=True,
                    check_policy_accessible=True,
                    check_external_access=False,
                )
            else:
                logger.debug(
                    "GCP SA Validity -Service account {} is NOT google-managed. Therefore, "
                    "we need to run all validation checks against it.".format(
                        service_account_id
                    )
                )
                service_account_validity_info.check_validity(
                    early_return=early_return,
                    check_type=True,
                    check_policy_accessible=True,
                    check_external_access=True,
                )

            # update project with error info from the service accounts
            new_service_account_validity.set(
                service_account_id, service_account_validity_info
            )

            if not service_account_validity_info:
                logger.warning("INVALID service account {}.".format(service_account_id))
                # if we need to return early for invalid SA, make sure to include
                # error details and invalidate the overall validity
                if early_return:
                    self.set("new_service_account", new_service_account_validity)
                    return

        self.set("new_service_account", new_service_account_validity)

        logger.debug(
            "Google Project with id: {} and number: {}. "
            "Beginning validation on project service accounts not requested for "
            "registration.".format(self.google_project_id, google_project_number)
        )

        service_accounts = get_service_account_ids_from_google_members(
            service_account_members
        )

        logger.debug("SAs on the project {}.".format(service_accounts))

        remove_white_listed_service_account_ids(service_accounts)

        # don't double check service account being registered
        if self.new_service_account:
            try:
                service_accounts.remove(self.new_service_account)
            except ValueError:
                logger.debug(
                    "Service Account requested for registration is not a "
                    "member of the Google project."
                )

        # use a generic validityinfo object to hold all the service accounts
        # validity. then check all the service accounts. Top level will be
        # invalid if any service accounts are invalid
        service_accounts_validity = ValidityInfo()
        for service_account in service_accounts:
            service_account_validity_info = self._get_project_sa_validity_info(
                service_account, google_project_number, early_return
            )

            # update project with error info from the service accounts
            service_accounts_validity.set(
                service_account, service_account_validity_info
            )

            if not service_account_validity_info and early_return:
                # if we need to return early for invalid SA, make sure to include
                # error details and invalidate the overall validity
                self.set("service_accounts", service_accounts_validity)
                return

        self.set("service_accounts", service_accounts_validity)

        logger.debug(
            "Checking data access for Google Project {}...".format(
                self.google_project_id
            )
        )

        # get the service accounts for the project to determine all the data
        # the project can access through the service accounts
        service_accounts = get_registered_service_accounts(
            self.google_project_id, db=db
        )
        service_account_project_access = get_project_access_from_service_accounts(
            service_accounts, db=db
        )

        logger.debug(
            "Registered SAs {} current have project access: {}".format(
                service_accounts, service_account_project_access
            )
        )

        # use a generic validityinfo object to hold all the projects validity
        project_access_validities = ValidityInfo()

        # extend list with any provided access to test
        for provided_access in self.new_service_account_access:
            project = get_project_from_auth_id(provided_access)

            # if provided access doesn't exist, set error in project_validity
            if not project:
                logger.warning(
                    "INVALID project access requested. "
                    "Data Access with auth_id {} does not exist.".format(
                        provided_access
                    )
                )
                project_validity = ValidityInfo()
                project_validity.set("exists", False)
                project_validity.set("all_users_have_access", None)
                project_access_validities.set(str(provided_access), project_validity)
            else:
                service_account_project_access.append(project)

        logger.debug(
            "New project access requested (in addition to "
            "previous access): {}.".format(service_account_project_access)
        )

        # make sure all the users of the project actually have access to all
        # the data the service accounts have access to
        for project in service_account_project_access:
            project_validity = ValidityInfo()
            project_validity.set("exists", True)

            # if all the users exist in our db, we can check if they have valid
            # access
            logger.debug(
                "Checking that all users in project have "
                "access to project with id {}".format(
                    getattr(project, "id", "ERROR-could-not-get-project-id")
                )
            )
            valid_access = None
            if users_in_project:
                valid_access = do_all_users_have_access_to_project(
                    users_in_project, project.id, db=db
                )
                if not valid_access:
                    logger.warning(
                        "INVALID Some users do NOT have "
                        "access to project with id {}. users in project: {}".format(
                            getattr(project, "id", "ERROR-could-not-get-project-id"),
                            [
                                getattr(user, "username", "unknown_user")
                                for user in users_in_project
                            ],
                        )
                    )

            project_validity.set("all_users_have_access", valid_access)

            project_access_validities.set(str(project.auth_id), project_validity)

        self.set("access", project_access_validities)
        self.google_cloud_manager.close()
        return
Example #3
0
    def check_validity(self, early_return=True, db=None, config=None):
        """
        Determine whether or not project is valid for registration. If
        early_return is False, this object will store information about the
        failure.

        Args:
            early_return (bool, optional): Description
        """
        google_project_number = get_google_project_number(
            self.google_project_id)
        has_access = bool(google_project_number)

        self.set("monitor_has_access", has_access)
        # always early return if we can't access the project
        if not has_access:
            return

        membership = get_google_project_membership(self.google_project_id)

        if self.user_id is not None:
            user_has_access = is_user_member_of_all_google_projects(
                self.user_id, [self.google_project_id],
                membership=membership,
                db=db)
            self.set("user_has_access", user_has_access)
            if not user_has_access:
                # always early return if user isn't a member on the project
                return

        parent_org = get_google_project_parent_org(self.google_project_id)
        valid_parent_org = True

        # if there is an org, let's remove whitelisted orgs and then check validity
        # again
        white_listed_google_parent_orgs = (
            config.get("WHITE_LISTED_GOOGLE_PARENT_ORGS") if config else None)

        if parent_org:
            valid_parent_org = is_org_whitelisted(
                parent_org,
                white_listed_google_parent_orgs=white_listed_google_parent_orgs,
            )

        self.set("valid_parent_org", valid_parent_org)

        if not valid_parent_org and early_return:
            return

        user_members = None
        service_account_members = []
        try:
            user_members, service_account_members = get_google_project_valid_users_and_service_accounts(
                self.google_project_id, membership=membership)
            self.set("valid_member_types", True)
        except Exception:
            self.set("valid_member_types", False)
            if early_return:
                return

        # if we have valid members, we can check if they exist in fence
        users_in_project = None
        if user_members is not None:
            try:
                users_in_project = get_users_from_google_members(user_members,
                                                                 db=db)
                self.set("members_exist_in_fence", True)
            except Exception:
                self.set("members_exist_in_fence", False)
                if early_return:
                    return

        # use a generic validityinfo object to hold all the service accounts
        # validity. then check all the service accounts. Top level will be
        # invalid if any service accounts are invalid
        new_service_account_validity = ValidityInfo()
        if self.new_service_account:
            service_account_validity_info = GoogleServiceAccountValidity(
                self.new_service_account, self.google_project_id,
                google_project_number)

            service_account_id = str(self.new_service_account)

            google_sa_domains = (
                config.get("GOOGLE_MANAGED_SERVICE_ACCOUNT_DOMAINS")
                if config else None)
            # we do NOT need to check the service account type and external access
            # for google-managed accounts.
            if is_google_managed_service_account(
                    service_account_id,
                    google_managed_service_account_domains=google_sa_domains,
            ):
                service_account_validity_info.check_validity(
                    early_return=early_return,
                    check_type_and_access=False,
                    config=config,
                )
            else:
                service_account_validity_info.check_validity(
                    early_return=early_return,
                    check_type_and_access=True,
                    config=config)

            # update project with error info from the service accounts
            new_service_account_validity.set(service_account_id,
                                             service_account_validity_info)

            if not service_account_validity_info and early_return:
                # if we need to return early for invalid SA, make sure to include
                # error details and invalidate the overall validity
                self.set("new_service_account", new_service_account_validity)
                return

        self.set("new_service_account", new_service_account_validity)

        service_accounts = get_service_account_ids_from_google_members(
            service_account_members)

        white_listed_service_accounts = (
            config.get("WHITE_LISTED_SERVICE_ACCOUNT_EMAILS")
            if config else None)
        app_creds_file = (config.get("GOOGLE_APPLICATION_CREDENTIALS")
                          if config else None)

        remove_white_listed_service_account_ids(
            service_accounts,
            app_creds_file=app_creds_file,
            white_listed_sa_emails=white_listed_service_accounts,
        )

        # use a generic validityinfo object to hold all the service accounts
        # validity. then check all the service accounts. Top level will be
        # invalid if any service accounts are invalid
        service_accounts_validity = ValidityInfo()
        for service_account in service_accounts:
            service_account_id = str(service_account)

            service_account_validity_info = GoogleServiceAccountValidity(
                service_account, self.google_project_id, google_project_number)

            google_sa_domains = (
                config.get("GOOGLE_MANAGED_SERVICE_ACCOUNT_DOMAINS")
                if config else None)
            # we do NOT need to check the service account type and external access
            # for google-managed accounts.
            if is_google_managed_service_account(
                    service_account_id,
                    google_managed_service_account_domains=google_sa_domains,
            ):
                service_account_validity_info.check_validity(
                    early_return=early_return,
                    check_type_and_access=False,
                    config=config,
                )
            else:
                service_account_validity_info.check_validity(
                    early_return=early_return,
                    check_type_and_access=True,
                    config=config)

            # update project with error info from the service accounts
            service_accounts_validity.set(service_account_id,
                                          service_account_validity_info)

            if not service_account_validity_info and early_return:
                # if we need to return early for invalid SA, make sure to include
                # error details and invalidate the overall validity
                self.set("service_accounts", service_accounts_validity)
                return

        self.set("service_accounts", service_accounts_validity)

        # get the service accounts for the project to determine all the data
        # the project can access through the service accounts
        service_accounts = get_registered_service_accounts(
            self.google_project_id, db=db)
        service_account_project_access = get_project_access_from_service_accounts(
            service_accounts, db=db)

        # use a generic validityinfo object to hold all the projects validity
        project_access_validities = ValidityInfo()

        # extend list with any provided access to test
        for provided_access in self.new_service_account_access:
            project = get_project_from_auth_id(provided_access)

            # if provided access doesn't exist, set error in project_validity
            if not project:
                project_validity = ValidityInfo()
                project_validity.set("exists", False)
                project_validity.set("all_users_have_access", None)
                project_access_validities.set(str(provided_access),
                                              project_validity)
            else:
                service_account_project_access.append(project)

        # make sure all the users of the project actually have access to all
        # the data the service accounts have access to
        for project in service_account_project_access:
            project_validity = ValidityInfo()
            project_validity.set("exists", True)

            # if all the users exist in our db, we can check if they have valid
            # access
            valid_access = None
            if users_in_project:
                valid_access = do_all_users_have_access_to_project(
                    users_in_project, project.id, db=db)
            project_validity.set("all_users_have_access", valid_access)

            project_access_validities.set(str(project.auth_id),
                                          project_validity)

        self.set("access", project_access_validities)
        return