Пример #1
0
    def get(self, request, fw_type=None, object_id=None):
        try:
            if object_id:
                try:
                    obj = LogOM().select_log_om(object_id).to_dict()
                except ObjectDoesNotExist:
                    return JsonResponse({'error': _('Object does not exist')},
                                        status=404)

            elif fw_type:
                try:
                    obj = [
                        logOM.to_dict()
                        for logOM in LOGFWD_MODELS[fw_type].objects.all()
                    ]
                except KeyError:
                    return JsonResponse({'error': _('Type does not exist')},
                                        status=404)

            else:
                obj = [
                    LogOM().select_log_om(logom.pk).to_dict()
                    for logom in LogOM.objects.all().only('pk')
                ]

            return JsonResponse({'data': obj})

        except Exception as e:
            logger.critical(e, exc_info=1)
            error = _("An error has occurred")

            if settings.DEV_MODE:
                error = str(e)

            return JsonResponse({'error': error}, status=500)
Пример #2
0
class LogOMTableForm(Form):
    """ Form used to generate table for log_forwarders """
    """ First condition word """
    condition = ChoiceField(
        label=_("Condition"),
        choices=CONDITION_CHOICES,
        widget=Select(attrs={'class': 'form-control select2'}))
    """ Comparison field name """
    field_name = ChoiceField(
        label=_("Field"),
        choices=FIELD_CHOICES,
        widget=Select(attrs={'class': 'form-control select2'}))
    """ Comparison operator """
    operator = ChoiceField(
        label=_("Operator"),
        choices=OPERATOR_CHOICES,
        widget=Select(attrs={'class': 'form-control select2'}))
    """ Comparison value """
    value = CharField(label=_("Value"),
                      initial='"plop"',
                      widget=TextInput(attrs={'class': 'form-control'}))
    """ Action - log forwarder """
    action = ModelMultipleChoiceField(
        label=_("Action"),
        queryset=LogOM.objects.all().only(*LogOM.str_attrs()),
        widget=SelectMultiple(attrs={'class': 'form-control select2'}))

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['action'].empty_label = None

    def as_table_headers(self):
        """ Format field names as table head """
        result = "<tr>\n"
        for field in self:
            result += "<th>{}</th>\n".format(field.label)
        result += "<th>Delete</th></tr>\n"
        return result

    def as_table_td(self):
        """ Format fields as a table with <td></td> """
        result = "<tr>"
        for field in self:
            result += "<td>{}</td>".format(field)
        result += "<td style='text-align:center'><a class='btnDelete'><i style='color:grey' " \
                  "class='fas fa-trash-alt'></i></a></td></tr>\n"
        return result
Пример #3
0
 def clean_log_condition(self):
     """ Verify if log_condition contains valids LogOM name into {{}} """
     regex = "{{([^}]+)}}"
     log_condition = self.cleaned_data.get('log_condition',
                                           "").replace("\r\n", "\n")
     for line in log_condition.split('\n'):
         if line.count('{') > 2:
             raise ValidationError("There cannot be 2 actions on one line.")
         match = re_search(regex, line)
         if match:
             try:
                 LogOM().select_log_om_by_name(match.group(1))
             except ObjectDoesNotExist:
                 raise ValidationError(
                     "Log Forwarder named '{}' not found.".format(
                         match.group(1)))
     return log_condition
Пример #4
0
def frontend_edit(request, object_id=None, api=False):
    frontend = None
    listener_form_list = []
    header_form_list = []
    reputationctx_form_list = []
    node_listeners = dict()
    if object_id:
        try:
            frontend = Frontend.objects.get(pk=object_id)
        except ObjectDoesNotExist:
            if api:
                return JsonResponse({'error': _("Object does not exist.")}, status=404)
            return HttpResponseForbidden("Injection detected")

    """ Create form with object if exists, and request.POST (or JSON) if exists """
    if hasattr(request, "JSON") and api:
        form = FrontendForm(request.JSON or None, instance=frontend, error_class=DivErrorList)
    else:
        form = FrontendForm(request.POST or None, instance=frontend, error_class=DivErrorList)

    def render_form(front, **kwargs):
        save_error = kwargs.get('save_error')
        if api:
            if form.errors:
                logger.error("Frontend api form error : {}".format(form.errors.get_json_data()))
                return JsonResponse(form.errors.get_json_data(), status=400)
            if save_error:
                logger.error("Frontend api save error : {}".format(save_error))
                return JsonResponse({'error': save_error[0]}, status=500)

        if not listener_form_list and front:
            for l_tmp in front.listener_set.all():
                listener_form_list.append(ListenerForm(instance=l_tmp))
        if not header_form_list and front:
            for h_tmp in front.headers.all():
                header_form_list.append(HeaderForm(instance=h_tmp))
        # If it is a new object, add default-example headers
        if not front and request.method == "GET":
            for header in DEFAULT_FRONTEND_HEADERS:
                header_form_list.append(HeaderForm(header))
        if not reputationctx_form_list and front:
            for r_tmp in front.frontendreputationcontext_set.all():
                reputationctx_form_list.append(FrontendReputationContextForm(instance=r_tmp))
        return render(request, 'services/frontend_edit.html',
                      {'form': form, 'listeners': listener_form_list, 'listener_form': ListenerForm(),
                       'headers': header_form_list, 'header_form': HeaderForm(),
                       'reputation_contexts': reputationctx_form_list,
                       'reputationctx_form': FrontendReputationContextForm(),
                       'log_om_table': LogOMTableForm(auto_id=False),
                       'redis_forwarder': LogOM.objects.filter(name="Internal_Dashboard").only('id').first().id,
                       'object_id': (frontend.id if frontend else "") or "", **kwargs})

    if request.method in ("POST", "PUT"):
        """ Handle JSON formatted listeners """
        try:
            if api:
                listener_ids = request.JSON.get('listeners', [])
                assert isinstance(listener_ids, list), "Listeners field must be a list."
            else:
                listener_ids = json_loads(request.POST.get('listeners', "[]"))
        except Exception as e:
            return render_form(frontend, save_error=["Error in Listeners field : {}".format(e),
                                                     str.join('', format_exception(*exc_info()))])
        header_objs = []
        reputationctx_objs = []
        if form.data.get('mode') == "http":
            """ Handle JSON formatted headers """
            try:
                if api:
                    header_ids = request.JSON.get('headers', [])
                    assert isinstance(header_ids, list), "Headers field must be a list."
                else:
                    header_ids = json_loads(request.POST.get('headers', "[]"))
            except Exception as e:
                return render_form(frontend, save_error=["Error in Request-headers field : {}".format(e),
                                                         str.join('', format_exception(*exc_info()))])

            """ For each header in list """
            for header in header_ids:
                """ If id is given, retrieve object from mongo """
                try:
                    instance_h = frontend.headers.get(pk=header['id']) if frontend and header['id'] else None
                except ObjectDoesNotExist:
                    form.add_error(None, "Request-header with id {} not found. Injection detected ?")
                    continue
                """ And instantiate form with the object, or None """
                header_f = HeaderForm(header, instance=instance_h)
                if not header_f.is_valid():
                    if api:
                        form.add_error(None, header_f.errors.get_json_data())
                    else:
                        form.add_error('headers', header_f.errors.as_ul())
                    continue
                # Save forms in case we re-print the page
                header_form_list.append(header_f)
                # And save objects list, to save them later, when Frontend will be saved
                header_objs.append(header_f.save(commit=False))

        """ Handle JSON formatted ReputationContext """
        try:
            if api:
                reputationctx_ids = request.JSON.get('reputation_contexts', [])
            else:
                reputationctx_ids = json_loads(request.POST.get('reputation_contexts', "[]"))
            assert isinstance(reputationctx_ids, list), "reputation_contexts field must be a list."
        except Exception as e:
            return render_form(frontend, save_error=["Error in ReputationConext field : {}".format(e),
                                                     str.join('', format_exception(*exc_info()))])

        """ For each reputation_context in list """
        for reputationctx in reputationctx_ids:
            """ Instantiate form """
            reputationctx_f = FrontendReputationContextForm(reputationctx)
            if not reputationctx_f.is_valid():
                form.add_error(None if api else "reputation_ctx",
                               reputationctx_f.errors.get_json_data() if api else reputationctx_f.errors.as_ul())
                continue
            # Save forms in case we re-print the page
            reputationctx_form_list.append(reputationctx_f)
            # And save objects list, to save them later, when Frontend will be saved
            reputationctx_objs.append(reputationctx_f.save(commit=False))

        listener_objs = []
        if form.data.get('mode') != "impcap" and form.data.get('listening_mode') != "file":
            """ For each listener in list """
            for listener in listener_ids:
                """ If id is given, retrieve object from mongo """
                try:
                    instance_l = Listener.objects.get(pk=listener['id']) if listener['id'] else None
                except ObjectDoesNotExist:
                    form.add_error(None, "Listener with id {} not found.".format(listener['id']))
                    continue

                """ And instantiate form with the object, or None """
                listener_f = ListenerForm(listener, instance=instance_l)
                if not listener_f.is_valid():
                    if api:
                        form.add_error("listeners", listener_f.errors.as_json())
                    else:
                        form.add_error("listeners", listener_f.errors.as_ul())
                    continue
                listener_form_list.append(listener_f)
                listener_obj = listener_f.save(commit=False)
                listener_objs.append(listener_obj)

                """ For each listener, get node """
                for nic in listener_obj.network_address.nic.all().only('node'):
                    if not node_listeners.get(nic.node):
                        node_listeners[nic.node] = list()
                    node_listeners[nic.node].append(listener_obj)

        # Get current Rsyslog configuration filename
        old_rsyslog_filename = frontend.get_rsyslog_base_filename() if frontend and frontend.enable_logging else ""

        # If errors has been added in form
        if not form.is_valid():
            logger.error("Frontend form errors: {}".format(form.errors.as_json()))
            return render_form(frontend)

        # Save the form to get an id if there is not already one
        frontend = form.save(commit=False)
        frontend.configuration = {}

        if frontend.mode == "impcap":
            node_listeners[frontend.impcap_intf.node] = []
        elif frontend.listening_mode == "file":
            node_listeners[frontend.node] = []

        # At least one Listener is required if Frontend enabled, except for listener of type "File" and "pcap"
        if not listener_objs and frontend.enabled and frontend.mode != "impcap" and frontend.listening_mode != "file":
            form.add_error(None, "At least one listener is required if frontend is enabled.")
            return render_form(frontend)

        try:
            """ For each node, the conf differs if listener chosen """
            """ Generate the conf """
            logger.debug("Generating conf of frontend '{}'".format(frontend.name))
            for node, listeners in node_listeners.items():
                # HAProxy conf does not use reputationctx_list, Rsyslog conf does
                frontend.configuration[node.name] = frontend.generate_conf(listener_list=listeners,
                                                                           header_list=header_objs)
            """ Save the conf on disk, and test-it with haproxy -c """
            logger.debug("Writing/Testing conf of frontend '{}'".format(frontend.name))
            frontend.test_conf()
        except ServiceError as e:
            logger.exception(e)
            return render_form(frontend, save_error=[str(e), e.traceback])

        except Exception as e:
            logger.exception(e)
            return render_form(frontend, save_error=["No referenced error",
                                                     str.join('', format_exception(*exc_info()))])

        """ If the object already exists """
        if frontend.id:
            try:
                changed_data = form.changed_data

                """ If the ruleset has changed, we need to delete the old-named file """
                if frontend.mode == "log" and "ruleset" in changed_data and old_rsyslog_filename:

                    # API request deletion of rsyslog frontend filename
                    Cluster.api_request('services.rsyslogd.rsyslog.delete_conf', old_rsyslog_filename)
                    logger.info("Rsyslogd config '{}' deletion asked.".format(old_rsyslog_filename))

                """ If it is an Rsyslog only conf """
                if (frontend.mode == "log" and frontend.listening_mode in ("udp", "file")) \
                        or frontend.mode == "impcap":

                    """ And if it was not before saving """
                    if "mode" in changed_data or "listening_mode" in changed_data:
                        """ Delete old HAProxy conf """
                        frontend_filename = frontend.get_base_filename()

                        # API request deletion of frontend filename
                        Cluster.api_request('services.haproxy.haproxy.delete_conf', frontend_filename)
                        logger.info("HAProxy config '{}' deletion asked.".format(frontend_filename))

                        # And reload of HAProxy service
                        Cluster.api_request('services.haproxy.haproxy.reload_service')

                    """ If it is an HAProxy only conf """
                elif frontend.mode not in ("log", "impcap") and not frontend.enable_logging:
                    """ And it was not """
                    if "mode" in changed_data or "enable_logging" in changed_data:
                        # API request deletion of rsyslog frontend filename
                        Cluster.api_request('services.rsyslogd.rsyslog.delete_conf', old_rsyslog_filename)
                        logger.info("Rsyslogd config '{}' deletion asked.".format(old_rsyslog_filename))

                        # And reload of Rsyslog service
                        Cluster.api_request('services.rsyslogd.rsyslog.restart_service')

            except Exception as e:
                logger.exception(e)
                return render_form(frontend, save_error=["Cluster API request failure: {}".format(e),
                                                         str.join('', format_exception(*exc_info()))])

        if form.errors:
            logger.error("Frontend form errors: {}".format(form.errors))
            return render_form(frontend)

        """ If the conf is OK, save the Frontend object """
        # Is that object already in db or not
        first_save = not frontend.id
        try:
            if frontend.mode == "http":
                frontend.ruleset = "haproxy"
            elif frontend.mode == "tcp":
                frontend.ruleset = "haproxy_tcp"
            elif frontend.mode == "impcap":
                frontend.ruleset = "impcap"

            if frontend.enable_logging:
                log_forwarders = set()
                """ Handle LogForwarders selected objects in log_condition """
                for log_om_name in re_findall("{{([^}]+)}}", frontend.log_condition):
                    try:
                        # Only id because we do not need any attribute
                        log_om = LogOM().select_log_om_by_name(log_om_name)
                        """ If the relation does not already exists : create-it """
                        log_forwarders.add(log_om.id)
                    except ObjectDoesNotExist:
                        form.add_error("log_condition", "LogForwarder not found.")
                        return render_form(frontend)

                frontend.log_forwarders_id = log_forwarders

            logger.debug("Saving frontend")
            frontend.save()
            logger.debug("Frontend '{}' (id={}) saved in MongoDB.".format(frontend.name, frontend.id))

            """ And all the listeners early created """
            for l in listener_objs:
                l.frontend = frontend
                logger.debug("Saving listener {}".format(str(l)))
                l.save()

            """ Delete listeners deleted in form """
            for l in frontend.listener_set.exclude(pk__in=[l.id for l in listener_objs]):
                l.delete()
                logger.info("Deleting listener {}".format(l))

            """ If mode is HTTP """
            if frontend.mode == "http":
                """ Remove request-headers removed """
                for header in frontend.headers.all():
                    if header not in header_objs:
                        frontend.headers.remove(header)

                """ Associate added request-headers """
                for header in header_objs:
                    new_object = not header.id
                    header.save()
                    if new_object:
                        frontend.headers.add(header)
                    logger.debug("HTTP Headers {} associated to Frontend {}".format(header, frontend))

            # Delete all intermediary objects
            FrontendReputationContext.objects.filter(frontend=frontend).delete()
            # And re-create them
            for reputationctx in reputationctx_objs:
                FrontendReputationContext.objects.create(frontend=frontend,
                                                         reputation_ctx=reputationctx.reputation_ctx,
                                                         enabled=reputationctx.enabled,
                                                         arg_field=reputationctx.arg_field)

            # Re-generate config AFTER save, to get ID
            for node, listeners in node_listeners.items():
                frontend.configuration[node.name] = frontend.generate_conf(listener_list=listeners)

            for node in node_listeners.keys():
                """ asynchronous API request to save conf on node """
                # Save conf first, to raise if there is an error
                frontend.save_conf(node)
                logger.debug("Write conf of frontend '{}' asked on node '{}'".format(frontend.name, node.name))

                """ We need to configure Rsyslog, it will check if conf has changed & restart service if needed """
                api_res = node.api_request("services.rsyslogd.rsyslog.build_conf", frontend.id)
                if not api_res.get('status'):
                    raise ServiceConfigError("on node {}\n API request error.".format(node.name), "rsyslog",
                                             traceback=api_res.get('message'))

                if not frontend.rsyslog_only_conf:
                    """ Reload HAProxy service - After rsyslog to prevent logging crash """
                    api_res = node.api_request("services.haproxy.haproxy.reload_service")
                    if not api_res.get('status'):
                        raise ServiceReloadError("on node {}\n API request error.".format(node.name), "haproxy",
                                                 traceback=api_res.get('message'))
                    frontend.status[node.name] = "WAITING"
                    frontend.save()

            # Check if reload logrotate conf if needed
            # meaning if there is a log_forwarder file enabled used by this frontend
            if frontend.enable_logging and (frontend.log_forwarders.filter(logomfile__enabled=True).count() > 0 or
                                            frontend.log_forwarders_parse_failure.filter(logomfile__enabled=True).count()):
                # Reload LogRotate config
                Cluster.api_request("services.logrotate.logrotate.reload_conf")

        except (VultureSystemError, ServiceError) as e:
            """ Error saving configuration file """
            """ The object has been saved, delete-it if needed """
            if first_save:
                for listener in frontend.listener_set.all():
                    listener.delete()

                frontend.delete()

            logger.exception(e)
            return render_form(frontend, save_error=[str(e), e.traceback])

        except Exception as e:
            """ If we arrive here, the object has not been saved """
            logger.exception(e)
            return render_form(frontend, save_error=["Failed to save object in database :\n{}".format(e),
                                                     str.join('', format_exception(*exc_info()))])

        if api:
            return build_response(frontend.id, "services.frontend.api", COMMAND_LIST)
        return HttpResponseRedirect('/services/frontend')

    return render_form(frontend)
Пример #5
0
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        """ Impcap/Log Darwin policy """
        self.fields['darwin_policy'] = ModelChoiceField(
            label=_("Darwin policy"),
            queryset=DarwinPolicy.objects.all(),
            widget=Select(attrs={'class': 'form-control select2'}),
            required=False)
        """ Log forwarders """
        self.fields['log_forwarders'] = ModelMultipleChoiceField(
            label=_("Log forwarders"),
            queryset=LogOM.objects.all().only(*LogOM.str_attrs()),
            widget=SelectMultiple(attrs={'class': 'form-control select2'}),
            required=False)
        """ Log forwarders """
        self.fields['log_forwarders_parse_failure'] = ModelMultipleChoiceField(
            label=_("Log forwarders - parse failure"),
            queryset=LogOM.objects.all().only(*LogOM.str_attrs()),
            widget=SelectMultiple(attrs={'class': 'form-control select2'}),
            required=False)
        """ MMDP Reputation database IPv4 """
        # Defined here AND in model, to use queryset
        self.fields['logging_reputation_database_v4'] = ModelChoiceField(
            label=_("Rsyslog IPv4 reputation database"),
            # queryset=[(f.get('id'), str(f)) for f in Feed.objects.mongo_find({"filename": {"$regex": r"\.mmdb$"},  # MMDB database
            #                                   "label": {"$regex": r"^((?![iI][Pp][Vv]6).)*$"}},  # Excude IPv6
            #                                  {"filename": 1, "label": 1})],  # .only( label, filename )
            # queryset=Feed.objects.filter(filename__iregex="^((?![iI][Pp][Vv]6).)*\.mmdb$"),
            # queryset=Feed.objects.exclude(filename__iregex="^((?![iI][Pp][Vv]6).)*$").filter(filename__endswith=".mmdb"),
            queryset=ReputationContext.objects.filter(
                db_type="ipv4", filename__endswith=".mmdb").only(
                    *(ReputationContext.str_attrs() +
                      ['filename', 'db_type'])),
            widget=Select(attrs={'class': 'form-control select2'}),
            empty_label="No IPv4",
            required=False)
        """ MMDP Reputation database IPv6 """
        # Defined here AND in model, to use queryset
        self.fields['logging_reputation_database_v6'] = ModelChoiceField(
            label=_("Rsyslog IPv6 reputation database"),
            queryset=ReputationContext.objects.filter(
                db_type="ipv6",
                filename__endswith=".mmdb")  # MMDP database & IPv6
            .only(*(ReputationContext.str_attrs() + ['filename', 'db_type'])),
            widget=Select(attrs={'class': 'form-control select2'}),
            empty_label="No IPv6",
            required=False)
        self.fields['logging_geoip_database'] = ModelChoiceField(
            label=_("Rsyslog GeoIP database"),
            queryset=ReputationContext.objects.filter(db_type="GeoIP").only(
                *(ReputationContext.str_attrs() + ['filename', 'db_type'])),
            widget=Select(attrs={'class': 'form-control select2'}),
            empty_label="No GeoIP",
            required=False)

        self.fields['node'] = ModelChoiceField(
            label=_('Node'),
            queryset=Node.objects.all(),
            widget=Select(attrs={'class': 'form-control select2'}))

        # Remove the blank input generated by django
        for field_name in [
                'mode', 'ruleset', 'log_level', 'listening_mode',
                'compression_algos', 'log_forwarders', 'impcap_intf'
        ]:
            self.fields[field_name].empty_label = None
        self.fields['error_template'].empty_label = "No template"
        # Set required in POST data to False
        for field_name in [
                'log_condition', 'ruleset', 'log_level', 'listening_mode',
                'headers', 'custom_haproxy_conf', 'cache_total_max_size',
                'cache_max_age', 'compression_algos', 'compression_mime_types',
                'error_template', 'enable_logging_reputation', 'impcap_filter',
                'impcap_filter_type', 'impcap_intf', 'tags', 'timeout_client',
                'timeout_connect', 'timeout_keep_alive', 'parser_tag',
                'file_path', 'node'
        ]:
            self.fields[field_name].required = False
        """ Build choices of "ruleset" field with rsyslog jinja templates names """
        # read the entries
        try:
            with scandir(JINJA_RSYSLOG_PATH) as listOfEntries:
                for entry in listOfEntries:
                    if entry.is_dir():
                        m = re_search("rsyslog_ruleset_([\w-]+)", entry.name)
                        if m:
                            # Do NOT process haproxy - it's an internal log type
                            if m.group(1) in ("haproxy", "haproxy_tcp"):
                                continue
                            self.fields['ruleset'].widget.choices.append(
                                (m.group(1), m.group(1)))
        except Exception as e:
            logger.error(
                "Cannot build 'ruleset' choices. Seems that path '{}' is not found: "
                .format(JINJA_RSYSLOG_PATH))
            logger.exception(e)

        # Set initial value of compression_algos field,
        #  convert space separated string into list
        if self.initial.get('compression_algos'):
            self.initial['compression_algos'] = self.initial.get(
                'compression_algos').split(' ')
        self.initial['tags'] = ','.join(
            self.initial.get('tags', []) or self.fields['tags'].initial)