Пример #1
0
class AlienVault:
    """AlienVault connector."""

    _CONFIG_NAMESPACE = "alienvault"

    _CONFIG_BASE_URL = f"{_CONFIG_NAMESPACE}.base_url"
    _CONFIG_API_KEY = f"{_CONFIG_NAMESPACE}.api_key"
    _CONFIG_TLP = f"{_CONFIG_NAMESPACE}.tlp"
    _CONFIG_CREATE_OBSERVABLES = f"{_CONFIG_NAMESPACE}.create_observables"
    _CONFIG_CREATE_INDICATORS = f"{_CONFIG_NAMESPACE}.create_indicators"
    _CONFIG_PULSE_START_TIMESTAMP = f"{_CONFIG_NAMESPACE}.pulse_start_timestamp"
    _CONFIG_REPORT_STATUS = f"{_CONFIG_NAMESPACE}.report_status"
    _CONFIG_REPORT_TYPE = f"{_CONFIG_NAMESPACE}.report_type"
    _CONFIG_GUESS_MALWARE = f"{_CONFIG_NAMESPACE}.guess_malware"
    _CONFIG_GUESS_CVE = f"{_CONFIG_NAMESPACE}.guess_cve"
    _CONFIG_EXCLUDED_PULSE_INDICATOR_TYPES = (
        f"{_CONFIG_NAMESPACE}.excluded_pulse_indicator_types")
    _CONFIG_INTERVAL_SEC = f"{_CONFIG_NAMESPACE}.interval_sec"

    _CONFIG_UPDATE_EXISTING_DATA = "connector.update_existing_data"

    _CONFIG_REPORT_STATUS_MAPPING = {
        "new": 0,
        "in progress": 1,
        "analyzed": 2,
        "closed": 3,
    }

    _DEFAULT_CREATE_OBSERVABLES = True
    _DEFAULT_CREATE_INDICATORS = True
    _DEFAULT_REPORT_TYPE = "threat-report"

    _CONNECTOR_RUN_INTERVAL_SEC = 60

    _STATE_LAST_RUN = "last_run"

    def __init__(self) -> None:
        """Initialize AlienVault connector."""
        config = self._read_configuration()

        # AlienVault connector configuration
        base_url = self._get_configuration(config, self._CONFIG_BASE_URL)
        api_key = self._get_configuration(config, self._CONFIG_API_KEY)

        tlp = self._get_configuration(config, self._CONFIG_TLP)
        tlp_marking = self._convert_tlp_to_marking_definition(tlp)

        create_observables = self._get_configuration(
            config, self._CONFIG_CREATE_OBSERVABLES)
        if create_observables is None:
            create_observables = self._DEFAULT_CREATE_OBSERVABLES
        else:
            create_observables = bool(create_observables)

        create_indicators = self._get_configuration(
            config, self._CONFIG_CREATE_INDICATORS)
        if create_indicators is None:
            create_indicators = self._DEFAULT_CREATE_INDICATORS
        else:
            create_indicators = bool(create_indicators)

        default_latest_pulse_timestamp = self._get_configuration(
            config, self._CONFIG_PULSE_START_TIMESTAMP)

        report_status_str = self._get_configuration(config,
                                                    self._CONFIG_REPORT_STATUS)
        report_status = self._convert_report_status_str_to_report_status_int(
            report_status_str)

        report_type = self._get_configuration(config, self._CONFIG_REPORT_TYPE)
        if not report_type:
            report_type = self._DEFAULT_REPORT_TYPE

        guess_malware = bool(
            self._get_configuration(config, self._CONFIG_GUESS_MALWARE))

        guess_cve = bool(
            self._get_configuration(config, self._CONFIG_GUESS_CVE))

        excluded_pulse_indicator_types_str = self._get_configuration(
            config, self._CONFIG_EXCLUDED_PULSE_INDICATOR_TYPES)
        excluded_pulse_indicator_types = set()
        if excluded_pulse_indicator_types_str is not None:
            excluded_pulse_indicator_types_list = convert_comma_separated_str_to_list(
                excluded_pulse_indicator_types_str)
            excluded_pulse_indicator_types = set(
                excluded_pulse_indicator_types_list)

        self.interval_sec = self._get_configuration(config,
                                                    self._CONFIG_INTERVAL_SEC,
                                                    is_number=True)

        update_existing_data = bool(
            self._get_configuration(config, self._CONFIG_UPDATE_EXISTING_DATA))

        # Create OpenCTI connector helper
        self.helper = OpenCTIConnectorHelper(config)

        # Create AlienVault author
        author = self._create_author()

        # Create AlienVault client
        client = AlienVaultClient(base_url, api_key)

        # Create pulse importer
        self.pulse_importer = PulseImporter(
            self.helper,
            client,
            author,
            tlp_marking,
            create_observables,
            create_indicators,
            update_existing_data,
            default_latest_pulse_timestamp,
            report_status,
            report_type,
            guess_malware,
            guess_cve,
            excluded_pulse_indicator_types,
        )

    @staticmethod
    def _create_author() -> Identity:
        return create_organization("AlienVault")

    @staticmethod
    def _read_configuration() -> Dict[str, str]:
        config_file_path = os.path.dirname(
            os.path.abspath(__file__)) + "/../config.yml"
        if not os.path.isfile(config_file_path):
            return {}
        return yaml.load(open(config_file_path), Loader=yaml.SafeLoader)

    @classmethod
    def _get_configuration(cls,
                           config: Dict[str, Any],
                           config_name: str,
                           is_number: bool = False) -> Any:
        yaml_path = cls._get_yaml_path(config_name)
        env_var_name = cls._get_environment_variable_name(yaml_path)
        config_value = get_config_variable(env_var_name,
                                           yaml_path,
                                           config,
                                           isNumber=is_number)
        return config_value

    @staticmethod
    def _get_yaml_path(config_name: str) -> List[str]:
        return config_name.split(".")

    @staticmethod
    def _get_environment_variable_name(yaml_path: List[str]) -> str:
        return "_".join(yaml_path).upper()

    @classmethod
    def _convert_tlp_to_marking_definition(
            cls, tlp_value: Optional[str]) -> MarkingDefinition:
        if tlp_value is None:
            return DEFAULT_TLP_MARKING_DEFINITION
        return get_tlp_string_marking_definition(tlp_value)

    @classmethod
    def _convert_report_status_str_to_report_status_int(
            cls, report_status: str) -> int:
        return cls._CONFIG_REPORT_STATUS_MAPPING[report_status.lower()]

    def run(self):
        """Run AlienVault connector."""
        self._info("Starting AlienVault connector...")
        while True:
            self._info("Running AlienVault connector...")
            run_interval = self._CONNECTOR_RUN_INTERVAL_SEC

            try:
                timestamp = self._current_unix_timestamp()
                current_state = self._load_state()

                self._info("Loaded state: {0}", current_state)

                last_run = self._get_state_value(current_state,
                                                 self._STATE_LAST_RUN)
                if self._is_scheduled(last_run, timestamp):
                    now = datetime.datetime.utcfromtimestamp(timestamp)
                    friendly_name = "AlienVault run @ " + now.strftime(
                        "%Y-%m-%d %H:%M:%S")
                    work_id = self.helper.api.work.initiate_work(
                        self.helper.connect_id, friendly_name)
                    pulse_import_state = self.pulse_importer.run(
                        current_state, work_id)
                    new_state = current_state.copy()
                    new_state.update(pulse_import_state)
                    new_state[
                        self._STATE_LAST_RUN] = self._current_unix_timestamp()

                    self._info("Storing new state: {0}", new_state)
                    self.helper.set_state(new_state)
                    message = ("State stored, next run in: " +
                               str(self._get_interval()) + " seconds")
                    self.helper.api.work.to_processed(work_id, message)
                    self._info(message)
                else:
                    next_run = self._get_interval() - (timestamp - last_run)
                    run_interval = min(run_interval, next_run)

                    self._info(
                        "Connector will not run, next run in: {0} seconds",
                        next_run)

                self._sleep(delay_sec=run_interval)
            except (KeyboardInterrupt, SystemExit):
                self._info("Connector stop")
                exit(0)
            except Exception as e:
                self._error("Internal error: {0}", str(e))
                self._sleep()

    @classmethod
    def _sleep(cls, delay_sec: Optional[int] = None) -> None:
        sleep_delay = (delay_sec if delay_sec is not None else
                       cls._CONNECTOR_RUN_INTERVAL_SEC)
        time.sleep(sleep_delay)

    @staticmethod
    def _current_unix_timestamp() -> int:
        return int(time.time())

    def _load_state(self) -> Dict[str, Any]:
        current_state = self.helper.get_state()
        if not current_state:
            return {}
        return current_state

    @staticmethod
    def _get_state_value(state: Optional[Mapping[str, Any]],
                         key: str,
                         default: Optional[Any] = None) -> Any:
        if state is not None:
            return state.get(key, default)
        return default

    def _is_scheduled(self, last_run: Optional[int],
                      current_time: int) -> bool:
        if last_run is None:
            self._info("Connector first run")
            return True
        time_diff = current_time - last_run
        return time_diff >= self._get_interval()

    def _get_interval(self) -> int:
        return int(self.interval_sec)

    def _info(self, msg: str, *args: Any) -> None:
        fmt_msg = msg.format(*args)
        self.helper.log_info(fmt_msg)

    def _error(self, msg: str, *args: Any) -> None:
        fmt_msg = msg.format(*args)
        self.helper.log_error(fmt_msg)
Пример #2
0
class AlienVault:
    """AlienVault connector."""

    _CONFIG_NAMESPACE = "alienvault"

    _CONFIG_BASE_URL = f"{_CONFIG_NAMESPACE}.base_url"
    _CONFIG_API_KEY = f"{_CONFIG_NAMESPACE}.api_key"
    _CONFIG_TLP = f"{_CONFIG_NAMESPACE}.tlp"
    _CONFIG_PULSE_START_TIMESTAMP = f"{_CONFIG_NAMESPACE}.pulse_start_timestamp"
    _CONFIG_REPORT_STATUS = f"{_CONFIG_NAMESPACE}.report_status"
    _CONFIG_REPORT_TYPE = f"{_CONFIG_NAMESPACE}.report_type"
    _CONFIG_GUESS_MALWARE = f"{_CONFIG_NAMESPACE}.guess_malware"
    _CONFIG_INTERVAL_SEC = f"{_CONFIG_NAMESPACE}.interval_sec"

    _CONFIG_UPDATE_EXISTING_DATA = "connector.update_existing_data"

    _CONFIG_TLP_MAPPING = {
        "white": TLP_WHITE,
        "green": TLP_GREEN,
        "amber": TLP_AMBER,
        "red": TLP_RED,
    }

    _CONFIG_REPORT_STATUS_MAPPING = {
        "new": 0,
        "in progress": 1,
        "analyzed": 2,
        "closed": 3,
    }

    _CONNECTOR_RUN_INTERVAL_SEC = 60

    _STATE_LAST_RUN = "last_run"

    def __init__(self) -> None:
        """Initialize AlienVault connector."""
        config = self._read_configuration()

        self.helper = OpenCTIConnectorHelper(config)

        # AlienVault connector configuration
        base_url = self._get_configuration(config, self._CONFIG_BASE_URL)
        api_key = self._get_configuration(config, self._CONFIG_API_KEY)

        tlp = self._get_configuration(config, self._CONFIG_TLP)
        tlp_marking = self._convert_tlp_to_marking_definition(tlp)

        default_latest_pulse_timestamp = self._get_configuration(
            config, self._CONFIG_PULSE_START_TIMESTAMP)

        report_status_str = self._get_configuration(config,
                                                    self._CONFIG_REPORT_STATUS)
        report_type = self._get_configuration(config, self._CONFIG_REPORT_TYPE)
        report_status = self._convert_report_status_str_to_report_status_int(
            report_status_str)

        guess_malware = bool(
            self._get_configuration(config, self._CONFIG_GUESS_MALWARE))

        self.interval_sec = self._get_configuration(config,
                                                    self._CONFIG_INTERVAL_SEC,
                                                    is_number=True)

        update_existing_data = bool(
            self._get_configuration(config, self._CONFIG_UPDATE_EXISTING_DATA))

        author = self._create_author()

        # Create AlienVault client
        client = AlienVaultClient(base_url, api_key)

        # Create pulse importer
        self.pulse_importer = PulseImporter(
            self.helper,
            client,
            author,
            tlp_marking,
            update_existing_data,
            default_latest_pulse_timestamp,
            report_status,
            report_type,
            guess_malware,
        )

    @staticmethod
    def _create_author() -> Identity:
        return Identity(
            name="AlienVault",
            identity_class="organization",
            custom_properties={CustomProperties.IDENTITY_TYPE: "organization"},
        )

    @staticmethod
    def _read_configuration() -> Dict[str, str]:
        config_file_path = os.path.dirname(
            os.path.abspath(__file__)) + "/../config.yml"
        if not os.path.isfile(config_file_path):
            return {}
        return yaml.load(open(config_file_path), Loader=yaml.FullLoader)

    @classmethod
    def _get_configuration(cls,
                           config: Dict[str, Any],
                           config_name: str,
                           is_number: bool = False) -> Any:
        yaml_path = cls._get_yaml_path(config_name)
        env_var_name = cls._get_environment_variable_name(yaml_path)
        config_value = get_config_variable(env_var_name,
                                           yaml_path,
                                           config,
                                           isNumber=is_number)
        return config_value

    @staticmethod
    def _get_yaml_path(config_name: str) -> List[str]:
        return config_name.split(".")

    @staticmethod
    def _get_environment_variable_name(yaml_path: List[str]) -> str:
        return "_".join(yaml_path).upper()

    @classmethod
    def _convert_tlp_to_marking_definition(
            cls, tlp_value: Optional[str]) -> MarkingDefinition:
        if tlp_value is None:
            return TLP_WHITE
        return cls._CONFIG_TLP_MAPPING[tlp_value.lower()]

    @classmethod
    def _convert_report_status_str_to_report_status_int(
            cls, report_status: str) -> int:
        return cls._CONFIG_REPORT_STATUS_MAPPING[report_status.lower()]

    def run(self):
        self._info("Starting AlienVault connector...")
        while True:
            self._info("Running AlienVault connector...")
            try:
                timestamp = self._current_unix_timestamp()
                current_state = self._load_state()

                self._info("Loaded state: {0}", current_state)

                last_run = self._get_state_value(current_state,
                                                 self._STATE_LAST_RUN)
                if self._is_scheduled(last_run, timestamp):
                    pulse_import_state = self.pulse_importer.run(current_state)

                    new_state = current_state.copy()
                    new_state.update(pulse_import_state)
                    new_state[
                        self._STATE_LAST_RUN] = self._current_unix_timestamp()

                    self._info("Storing new state: {0}", new_state)

                    self.helper.set_state(new_state)

                    self._info("State stored, next run in: {0} seconds",
                               self._get_interval())
                else:
                    next_run = self._get_interval() - (timestamp - last_run)
                    self._info(
                        "Connector will not run, next run in: {0} seconds",
                        next_run)

                self._sleep()
            except (KeyboardInterrupt, SystemExit):
                self._info("Connector stop")
                exit(0)
            except Exception as e:
                self._error(str(e))
                self._sleep()

    @classmethod
    def _sleep(cls) -> None:
        time.sleep(cls._CONNECTOR_RUN_INTERVAL_SEC)

    @staticmethod
    def _current_unix_timestamp() -> int:
        return int(time.time())

    def _load_state(self) -> Dict[str, Any]:
        current_state = self.helper.get_state()
        if not current_state:
            return {}
        return current_state

    @staticmethod
    def _get_state_value(state: Optional[Mapping[str, Any]],
                         key: str,
                         default: Optional[Any] = None) -> Any:
        if state is not None:
            return state.get(key, default)
        return default

    def _is_scheduled(self, last_run: Optional[int],
                      current_time: int) -> bool:
        if last_run is None:
            self._info("Connector first run")
            return True
        time_diff = current_time - last_run
        return time_diff >= self._get_interval()

    def _get_interval(self) -> int:
        return int(self.interval_sec)

    def _info(self, msg: str, *args: Any) -> None:
        fmt_msg = msg.format(*args)
        self.helper.log_info(fmt_msg)

    def _error(self, msg: str, *args: Any) -> None:
        fmt_msg = msg.format(*args)
        self.helper.log_error(fmt_msg)