ok
Direktori : /opt/imunify360/venv/lib/python3.11/site-packages/im360/ |
Current File : //opt/imunify360/venv/lib/python3.11/site-packages/im360/aibolit_job.py |
#!/usr/bin/env python3 """Helper functions to kick off the aibolit scan on file upload. See {modsec,pureftpd}-on-upload scripts for the example usage. """ import base64 import json import os import socket import time import uuid from contextlib import ExitStack, suppress from pathlib import Path from tempfile import NamedTemporaryFile # NOTE: despite being placed inside im360, the aibolit_job package # shouldn't import anything from the agent __all__ = [ "RESIDENT_DIR", "RESIDENT_IN_DIR_NOTIFY_REL_PATH", "RESIDENT_IN_DIR_UPLOAD_REL_PATH", "UPLOAD_TIMEOUT", "create_notify_job", "create_remaining_time_func", "create_upload_job", "notify_aibolit_start_it_if_necessary", ] AIBOLIT_STARTUP_SOCKET = "/var/run/defence360agent/aibolit-resident.sock" RESIDENT_DIR = Path("/var/imunify360/aibolit/resident") # path for uploads jobs relative to RESIDENT_DIR RESIDENT_IN_DIR_UPLOAD_REL_PATH = Path("in/upload-jobs") # path for notify jobs relative to RESIDENT_DIR RESIDENT_IN_DIR_NOTIFY_REL_PATH = Path("in/notify-jobs") STUCK_TIMEOUT = 5 UPLOAD_TIMEOUT = 10 # file lock timeout STARTUP_SOCKET_TIMEOUT = 0.1 def create_remaining_time_func( timeout, *, start_time=None, timer=time.monotonic ): """Create remaining_time() function. Start the timer if start_time is None otherwise use given value as the start time. remaining_time() raises TimeoutError in *timeout* seconds after the *start_time* according to the *timer*. """ start_time = timer() def remaining_time(): time_left = timeout - (timer() - start_time) if time_left <= 0: raise TimeoutError return time_left return remaining_time def create_upload_job(files, *, resident_dir_path=RESIDENT_DIR, timeout=None): """Create PID.upload_job in the resident/in dir.""" create_job( files, job_path=resident_dir_path / RESIDENT_IN_DIR_UPLOAD_REL_PATH / "{}.upload_job".format(os.getpid()), timeout=timeout, ) def create_notify_job(files, *, resident_dir_path=RESIDENT_DIR, timeout=None): """Create UUID.notify_job in the resident/in dir.""" create_job( files, job_path=resident_dir_path / RESIDENT_IN_DIR_NOTIFY_REL_PATH / "{}.notify_job".format(uuid.uuid4()), timeout=timeout, ) def create_job(files, *, job_path, timeout=None): # note: timeout is ignored to avoid complicating the common case # when the file operations take less than the timeout files = [base64.b64encode(os.fsencode(fn)).decode("ascii") for fn in files] write_atomic( job_path, json.dumps({"files": files}).encode("ascii"), ) def notify_aibolit_start_it_if_necessary(timeout=STARTUP_SOCKET_TIMEOUT): """Notify aibolit that there is a file to scan. Start/restart aibolit if necessary. """ # sending a byte to the aibolit's startup socket starts aibolit if it # is not running already # note: assume that if aibolit is running then it # picks up the job itself with socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) as sock: sock.settimeout(timeout) sock.connect(AIBOLIT_STARTUP_SOCKET) sock.send(b"1") def write_atomic(path: Path, content: bytes): """Write *content* to *path* atomically. Ignore fsync() issues: https://stackoverflow.com/questions/12003805/threadsafe-and-fault-tolerant-file-writes """ with ExitStack() as stack: with NamedTemporaryFile( dir=str(path.parent), delete=False ) as output_file: # call cleanup on error in write() or replace() def cleanup(): with suppress(FileNotFoundError): os.remove(output_file.name) stack.callback(cleanup) output_file.write(content) Path(output_file.name).replace(path) stack.pop_all() # success, don't call cleanup