ok
Direktori : /opt/imunify360/venv/lib/python3.11/site-packages/im360/simple_rpc/ |
Current File : //opt/imunify360/venv/lib/python3.11/site-packages/im360/simple_rpc/proactive.py |
import pwd import itertools import logging import os from pathlib import Path from typing import Optional from struct import pack from peewee import DoesNotExist, JOIN from contextlib import suppress from defence360agent.feature_management.lookup import feature from defence360agent.feature_management.constants import PROACTIVE, FULL from defence360agent.model import instance from defence360agent.rpc_tools import ValidationError from defence360agent.rpc_tools.lookup import CommonEndpoints, bind from defence360agent.utils import Scope from im360.model.proactive import ( Proactive, ProactiveIgnoredPath, ProactiveIgnoredRule, ) MAX_WHITELIST_RULES = 32 MAX_FILE_STRING = 4096 API_VER = 3 WHITELIST_FILENAME = "/usr/share/i360-php-opts/rules_whitelist" IGNORE_ENTRY_PACK_PATTERN = "I{}s{}I".format( MAX_FILE_STRING, MAX_WHITELIST_RULES ) logger = logging.getLogger(__name__) @feature(PROACTIVE, [FULL]) class ProactiveEndpoints(CommonEndpoints): SCOPE = Scope.IM360 def _make_bytes_from_path_entry(self, path, rules, path_id): """ :param path: some path string like "/some/path" :param rules: some array with ints like [1, 2, 3] or [] :param path_id: :return: """ rules += [0] * (MAX_WHITELIST_RULES - len(rules)) return pack( IGNORE_ENTRY_PACK_PATTERN, path_id, path.encode("utf8"), *rules ) def _generate_ignore_list_file(self): logger.info("Recreating proactive ignore list file") paths = ProactiveIgnoredPath.select( ProactiveIgnoredPath, ProactiveIgnoredRule ).join(ProactiveIgnoredRule, JOIN.LEFT_OUTER) whitelist_file_bytes = b"" path_number = 1 for path, rules in itertools.groupby(paths, lambda x: x.path): rules = [ r.proactiveignoredrule.rule_id for r in rules if getattr(r, "proactiveignoredrule", None) ] whitelist_file_bytes += self._make_bytes_from_path_entry( path, rules, path_number ) path_number += 1 if whitelist_file_bytes: try: with open(WHITELIST_FILENAME, "wb") as whitelist_file: whitelist_file.write(pack("I", API_VER)) whitelist_file.write(whitelist_file_bytes) os.chmod(WHITELIST_FILENAME, 0o644) logger.info("Proactive ignore list file successfully updated") except FileNotFoundError as e: logger.warning(str(e)) else: with suppress(FileNotFoundError): os.remove(WHITELIST_FILENAME) logger.info("Proactive ignore list file successfully cleaned") def _get_uid(self, username): if username is None: return 0 else: return pwd.getpwnam(username).pw_uid def _get_homedir(self, username): if username is not None: return Path(pwd.getpwnam(username).pw_dir) else: return None @bind("proactive", "list") async def proactive_list(self, user=None, **kwargs): return Proactive.fetch(uid=self._get_uid(user), **kwargs) @bind("proactive", "details") async def proactive_details(self, id, user=None): try: return {"items": Proactive.details(id, self._get_uid(user))} except DoesNotExist: raise ValidationError("Event {} does not exist".format(id)) @bind("proactive", "ignore", "list") async def proactive_ignore_list(self, user=None, **kwargs): return ProactiveIgnoredPath.fetch(self._get_homedir(user), **kwargs) def _validate_ignore_item( self, homedir: Optional[Path], path: str, rule_id: Optional[int] = None, rule_name: Optional[str] = None, ): if rule_id is not None and rule_name is None: raise ValidationError( "rule_name should be specified if rule_id is specified" ) if homedir is not None: # check if user tries to ignore file from his home directory # pw = pwd.getpwnam(user) if homedir not in Path(path).parents: raise ValidationError( "Unable to add {} to ignore: not permitted".format(path) ) def _add_ignore_item( self, path: str, rule_id: Optional[int] = None, rule_name: Optional[str] = None, ): with instance.db.atomic(): ignored_path, created = ProactiveIgnoredPath.create_or_get( path=path ) if (not created) and (ignored_path.rules.count() == 0): # path is already ignored for all rules return if rule_id: ProactiveIgnoredRule.create_or_get( path=ignored_path, rule_id=rule_id, rule_name=rule_name ) else: # if rule id is not provided, all rules are ignored ProactiveIgnoredRule.delete().where( ProactiveIgnoredRule.path == ignored_path ).execute() @bind("proactive", "ignore", "addmany") async def proactive_ignore_addmany(self, items, user=None): homedir = self._get_homedir(user) for item in items: self._validate_ignore_item(homedir, **item) for item in items: self._add_ignore_item(**item) self._generate_ignore_list_file() @bind("proactive", "ignore", "add") async def proactive_ignore_add( self, path, rule_id=None, rule_name=None, user=None ): self._validate_ignore_item( self._get_homedir(user), path, rule_id, rule_name ) self._add_ignore_item(path, rule_id, rule_name) self._generate_ignore_list_file() @bind("proactive", "ignore", "delete", "path") async def proactive_ignore_delete(self, paths, user=None): if user is not None: homedir = self._get_homedir(user) for p in paths: if homedir not in Path(p).parents: raise ValidationError( "Unable to delete {}: not permitted".format(p) ) for p in paths: ProactiveIgnoredPath.delete().where( ProactiveIgnoredPath.path == p ).execute() self._generate_ignore_list_file() @bind("proactive", "ignore", "delete", "rule") async def proactive_ignore_delete_rule(self, path, id, user=None): homedir = self._get_homedir(user) if (homedir is not None) and (homedir not in Path(path).parents): raise ValidationError("Unable do delete rule: not permitted") with instance.db.atomic(): try: path_obj = ProactiveIgnoredPath.get(path=path) except DoesNotExist: raise ValidationError("Path not found") num_deleted = ( ProactiveIgnoredRule.delete() .where( ProactiveIgnoredRule.path_id == path_obj.path, ProactiveIgnoredRule.rule_id == id, ) .execute() ) if num_deleted: # check if there are still any rules ignored for this path num_rules = ( ProactiveIgnoredRule.select() .where(ProactiveIgnoredRule.path_id == path_obj.path) .count() ) # if we deleted last rule, path should be also deleted if not num_rules: path_obj.delete_instance() self._generate_ignore_list_file()