def run_action(version_id): log.info('Checking rules and actions for version %s.', version_id) version = Version.objects.get(pk=version_id) rule = ( ScannerRule.objects.filter(scannerresult__version=version, is_active=True).order_by( # The `-` sign means descending order. '-action').first()) if not rule: log.info('No action to execute for version %s.', version_id) return action_id = rule.action action_name = ACTIONS.get(action_id, None) if not action_name: raise Exception("invalid action %s" % action_id) ACTION_FUNCTIONS = { NO_ACTION: _no_action, FLAG_FOR_HUMAN_REVIEW: _flag_for_human_review, } action_function = ACTION_FUNCTIONS.get(action_id, None) if not action_function: raise Exception("no implementation for action %s" % action_id) # We have a valid action to execute, so let's do it! log.info('Starting action "%s" for version %s.', action_name, version_id) action_function(version) log.info('Ending action "%s" for version %s.', action_name, version_id)
class AbstractScannerRule(ModelBase): name = models.CharField( max_length=200, help_text=_('This is the exact name of the rule used by a scanner.'), ) scanner = models.PositiveSmallIntegerField(choices=SCANNERS.items()) action = models.PositiveSmallIntegerField(choices=ACTIONS.items(), default=NO_ACTION) is_active = models.BooleanField( default=True, help_text=_( 'When unchecked, the scanner results will not be bound to this ' 'rule and the action will not be executed.')) definition = models.TextField(null=True, blank=True) class Meta(ModelBase.Meta): abstract = True unique_together = ('name', 'scanner') def __str__(self): return self.name def clean(self): if self.scanner == YARA: self.clean_yara() def clean_yara(self): if not self.definition: raise ValidationError( {'definition': _('Yara rules should have a definition')}) if 'rule {}'.format(self.name) not in self.definition: raise ValidationError({ 'definition': _('The name of the rule in the definition should match ' 'the name of the scanner rule') }) if len(re.findall(r'rule\s+.+?\s+{', self.definition)) > 1: raise ValidationError({ 'definition': _('Only one Yara rule is allowed in the definition') }) try: yara.compile(source=self.definition) except yara.SyntaxError as syntaxError: raise ValidationError({ 'definition': _('The definition is not valid: %(error)s') % { 'error': syntaxError, } }) except Exception: raise ValidationError({ 'definition': _('An error occurred when compiling the definition') })
def run_action(cls, version): """Try to find and execute an action for a given version, based on the scanner results and associated rules. If an action is found, it is run synchronously from this method, not in a task. """ log.info('Checking rules and actions for version %s.', version.pk) rule_model = cls.matched_rules.rel.model result_query_name = cls._meta.get_field( 'matched_rules').related_query_name() rule = ( rule_model.objects.filter(**{ f'{result_query_name}__version': version, 'is_active': True, }) .order_by( # The `-` sign means descending order. '-action' ) .first() ) if not rule: log.info('No action to execute for version %s.', version.pk) return action_id = rule.action action_name = ACTIONS.get(action_id, None) if not action_name: raise Exception("invalid action %s" % action_id) ACTION_FUNCTIONS = { NO_ACTION: _no_action, FLAG_FOR_HUMAN_REVIEW: _flag_for_human_review, DELAY_AUTO_APPROVAL: _delay_auto_approval, DELAY_AUTO_APPROVAL_INDEFINITELY: ( _delay_auto_approval_indefinitely), } action_function = ACTION_FUNCTIONS.get(action_id, None) if not action_function: raise Exception("no implementation for action %s" % action_id) # We have a valid action to execute, so let's do it! log.info( 'Starting action "%s" for version %s.', action_name, version.pk) action_function(version) log.info('Ending action "%s" for version %s.', action_name, version.pk)
class ScannerRule(ModelBase): name = models.CharField( max_length=200, help_text=_('This is the exact name of the rule used by a scanner.'), ) scanner = models.PositiveSmallIntegerField(choices=SCANNERS.items()) action = models.PositiveSmallIntegerField(choices=ACTIONS.items(), default=NO_ACTION) is_active = models.BooleanField(default=True) class Meta: db_table = 'scanners_rules' unique_together = ('name', 'scanner') def __str__(self): return self.name
def run_action(version_id): """This function tries to find an action to execute for a given version, based on the scanner results and associated rules. It is not run as a Celery task but as a simple function, in the auto_approve CRON.""" log.info('Checking rules and actions for version %s.', version_id) version = Version.objects.get(pk=version_id) rule = ( ScannerRule.objects.filter( scannerresult__version=version, is_active=True ) .order_by( # The `-` sign means descending order. '-action' ) .first() ) if not rule: log.info('No action to execute for version %s.', version_id) return action_id = rule.action action_name = ACTIONS.get(action_id, None) if not action_name: raise Exception("invalid action %s" % action_id) ACTION_FUNCTIONS = { NO_ACTION: _no_action, FLAG_FOR_HUMAN_REVIEW: _flag_for_human_review, DELAY_AUTO_APPROVAL: _delay_auto_approval, DELAY_AUTO_APPROVAL_INDEFINITELY: _delay_auto_approval_indefinitely, } action_function = ACTION_FUNCTIONS.get(action_id, None) if not action_function: raise Exception("no implementation for action %s" % action_id) # We have a valid action to execute, so let's do it! log.info('Starting action "%s" for version %s.', action_name, version_id) action_function(version) log.info('Ending action "%s" for version %s.', action_name, version_id)
def run_action(cls, version): """Try to find and execute an action for a given version, based on the scanner results and associated rules. If an action is found, it is run synchronously from this method, not in a task. """ log.info('Checking rules and actions for version %s.', version.pk) try: mad_result = cls.objects.filter(version=version, scanner=MAD).get() customs = mad_result.results.get('scanners', {}).get('customs', {}) customs_score = customs.get('score', 0.5) customs_models_agree = customs.get('result_details', {}).get( 'models_agree', True ) if ( customs_score <= 0.01 or customs_score >= 0.99 or not customs_models_agree ): log.info('Flagging version %s for human review by MAD.', version.pk) _flag_for_human_review_by_scanner(version, MAD) except cls.DoesNotExist: log.info('No MAD scanner result for version %s.', version.pk) pass rule_model = cls.matched_rules.rel.model result_query_name = cls._meta.get_field( 'matched_rules' ).related_query_name() rule = ( rule_model.objects.filter( **{f'{result_query_name}__version': version, 'is_active': True} ) .order_by( # The `-` sign means descending order. '-action' ) .first() ) if not rule: log.info('No action to execute for version %s.', version.pk) return action_id = rule.action action_name = ACTIONS.get(action_id, None) if not action_name: raise Exception("invalid action %s" % action_id) ACTION_FUNCTIONS = { NO_ACTION: _no_action, FLAG_FOR_HUMAN_REVIEW: _flag_for_human_review, DELAY_AUTO_APPROVAL: _delay_auto_approval, DELAY_AUTO_APPROVAL_INDEFINITELY: ( _delay_auto_approval_indefinitely ), } action_function = ACTION_FUNCTIONS.get(action_id, None) if not action_function: raise Exception("no implementation for action %s" % action_id) # We have a valid action to execute, so let's do it! log.info( 'Starting action "%s" for version %s.', action_name, version.pk ) action_function(version) log.info('Ending action "%s" for version %s.', action_name, version.pk)
class AbstractScannerRule(ModelBase): name = models.CharField( max_length=200, help_text=_('This is the exact name of the rule used by a scanner.'), ) scanner = models.PositiveSmallIntegerField(choices=SCANNERS.items()) action = models.PositiveSmallIntegerField(choices=ACTIONS.items(), default=NO_ACTION) is_active = models.BooleanField( default=True, help_text=_( 'When unchecked, the scanner results will not be bound to this ' 'rule and the action will not be executed.'), ) definition = models.TextField(null=True, blank=True) class Meta(ModelBase.Meta): abstract = True unique_together = ('name', 'scanner') @classmethod def get_yara_externals(cls): """ Return a dict with the various external variables we inject in every yara rule automatically and their default values. """ return { 'is_json_file': False, 'is_manifest_file': False, 'is_locale_file': False, } def __str__(self): return self.name def clean(self): if self.scanner == YARA: self.clean_yara() def clean_yara(self): if not self.definition: raise ValidationError( {'definition': _('Yara rules should have a definition')}) if f'rule {self.name}' not in self.definition: raise ValidationError({ 'definition': _('The name of the rule in the definition should match ' 'the name of the scanner rule') }) if len(re.findall(r'rule\s+.+?\s+{', self.definition)) > 1: raise ValidationError({ 'definition': _('Only one Yara rule is allowed in the definition') }) try: yara.compile(source=self.definition, externals=self.get_yara_externals()) except yara.SyntaxError as syntaxError: raise ValidationError({ 'definition': _('The definition is not valid: %(error)s') % { 'error': syntaxError } }) except Exception: raise ValidationError({ 'definition': _('An error occurred when compiling the definition') })