From c2313a331bdb5b7afd605c582270e0ab5e11aac3 Mon Sep 17 00:00:00 2001 From: tvandenabbeel-spotit Date: Fri, 4 Oct 2024 08:50:38 +0000 Subject: [PATCH 01/26] Fix ref POSTGRES_USER Fix reference to POSTGRES_USER variable in postgres template for kubernetes --- deploy/kubernetes/charts/templates/postgres.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/kubernetes/charts/templates/postgres.yaml b/deploy/kubernetes/charts/templates/postgres.yaml index fa3e6066d..a0ea62ae1 100644 --- a/deploy/kubernetes/charts/templates/postgres.yaml +++ b/deploy/kubernetes/charts/templates/postgres.yaml @@ -59,7 +59,7 @@ spec: value: {{ .Values.postgres.POSTGRES_DB | quote }} - name: POSTGRES_USER # Setting Database username - value: {{ .Values.postgres.POSTGRES_ADMIN_USER | quote }} + value: {{ .Values.postgres.POSTGRES_USER | quote }} - name: POSTGRES_PASSWORDD # Setting Database password value: {{ .Values.postgres.POSTGRES_PASSWORD | quote }} @@ -101,4 +101,4 @@ spec: - port: {{ .Values.postgres.service.port }} selector: app: {{ .Values.postgres.app }} ---- \ No newline at end of file +--- From 88eb441e83c70c874de413ad727f53d7438d38bc Mon Sep 17 00:00:00 2001 From: Vladimir-A <32281993+Vladimir-A@users.noreply.github.com> Date: Sat, 12 Oct 2024 10:57:56 -0400 Subject: [PATCH 02/26] [FIX] realization on_preload_task_delete --- source/app/blueprints/case/case_tasks_routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/app/blueprints/case/case_tasks_routes.py b/source/app/blueprints/case/case_tasks_routes.py index 999500ce3..87456fc93 100644 --- a/source/app/blueprints/case/case_tasks_routes.py +++ b/source/app/blueprints/case/case_tasks_routes.py @@ -256,10 +256,10 @@ def case_edit_task(cur_id, caseid): @case_tasks_blueprint.route('/case/tasks/delete/', methods=['POST']) @ac_api_case_requires(CaseAccessLevel.full_access) def case_delete_task(cur_id, caseid): - call_modules_hook('on_preload_task_delete', data=cur_id, caseid=caseid) task = get_task_with_assignees(task_id=cur_id, case_id=caseid) if not task: return response_error("Invalid task ID for this case") + call_modules_hook('on_preload_task_delete', data=task, caseid=caseid) delete_task(task.id) From d02fe073a62db82a0a796f6a60b85221936b6e73 Mon Sep 17 00:00:00 2001 From: Vladimir-A <32281993+Vladimir-A@users.noreply.github.com> Date: Sat, 12 Oct 2024 11:40:40 -0400 Subject: [PATCH 03/26] [FIX] realization on_preload_case_delete --- source/app/business/cases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/app/business/cases.py b/source/app/business/cases.py index fe41d1386..10700073e 100644 --- a/source/app/business/cases.py +++ b/source/app/business/cases.py @@ -131,7 +131,7 @@ def delete(case_identifier): raise BusinessProcessingError('Cannot delete a primary case to keep consistency') try: - call_modules_hook('on_preload_case_delete', data=case_identifier, caseid=case_identifier) + call_modules_hook('on_preload_case_delete', data=get_case(case_identifier), caseid=case_identifier) if not delete_case(case_identifier): track_activity(f'tried to delete case {case_identifier}, but it doesn\'t exist', caseid=case_identifier, ctx_less=True) From e7d7c5f246ec319982707aa701ca6478c3c683ed Mon Sep 17 00:00:00 2001 From: whikernel Date: Wed, 23 Oct 2024 14:28:27 +0200 Subject: [PATCH 04/26] [FIX] Memory issue in alerts --- source/app/blueprints/alerts/alerts_routes.py | 11 +++++++++++ source/app/datamgmt/alerts/alerts_db.py | 9 ++++----- source/app/datamgmt/case/case_notes_db.py | 1 - 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/source/app/blueprints/alerts/alerts_routes.py b/source/app/blueprints/alerts/alerts_routes.py index 5b0d540b0..cbc743ed9 100644 --- a/source/app/blueprints/alerts/alerts_routes.py +++ b/source/app/blueprints/alerts/alerts_routes.py @@ -15,6 +15,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +import tracemalloc + import json import marshmallow from datetime import datetime @@ -135,6 +137,9 @@ def alerts_list_route() -> Response: resolution_status=request.args.get('alert_resolution_id', type=int), current_user_id=current_user.id ) + import gc + gc.collect() + snapshot_before_selectinload = tracemalloc.take_snapshot() if filtered_data is None: return response_error('Filtering error') @@ -147,6 +152,12 @@ def alerts_list_route() -> Response: 'next_page': filtered_data.next_num if filtered_data.has_next else None, } + snapshot_after_selectinload = tracemalloc.take_snapshot() + top_stats = snapshot_after_selectinload.compare_to(snapshot_before_selectinload, 'lineno') + print("[ Top 10 differences ]") + for stat in top_stats[:10]: + print(stat) + return response_success(data=alerts) diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index 8e60716d4..c6d777b17 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -23,8 +23,7 @@ from functools import reduce from operator import and_ from sqlalchemy import desc, asc, func, tuple_, or_ -from sqlalchemy.orm import aliased, make_transient -from sqlalchemy.orm import joinedload +from sqlalchemy.orm import aliased, make_transient, selectinload from typing import List, Tuple import app @@ -185,8 +184,8 @@ def get_filtered_alerts( ).filter( *conditions ).options( - joinedload(Alert.severity), joinedload(Alert.status), joinedload(Alert.customer), joinedload(Alert.cases), - joinedload(Alert.iocs), joinedload(Alert.assets) + selectinload(Alert.severity), selectinload(Alert.status), selectinload(Alert.customer), selectinload(Alert.cases), + selectinload(Alert.iocs), selectinload(Alert.assets) ).order_by( order_func(Alert.alert_source_event_time) ).paginate(page=page, per_page=per_page, error_out=False) @@ -248,7 +247,7 @@ def get_alert_by_id(alert_id: int) -> Alert: """ return ( db.session.query(Alert) - .options(joinedload(Alert.iocs), joinedload(Alert.assets)) + .options(selectinload(Alert.iocs), selectinload(Alert.assets)) .filter(Alert.alert_id == alert_id) .first() ) diff --git a/source/app/datamgmt/case/case_notes_db.py b/source/app/datamgmt/case/case_notes_db.py index a4d16478c..5871c1e25 100644 --- a/source/app/datamgmt/case/case_notes_db.py +++ b/source/app/datamgmt/case/case_notes_db.py @@ -17,7 +17,6 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from flask_login import current_user from sqlalchemy import and_ -from sqlalchemy.orm import joinedload from app import db from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes From be4d6cb0515ea6c461e35d008f8f45e95ca106a1 Mon Sep 17 00:00:00 2001 From: whikernel Date: Wed, 23 Oct 2024 15:22:38 +0200 Subject: [PATCH 05/26] [ADD] Added source reference as filter in alerts --- source/app/blueprints/alerts/alerts_routes.py | 12 +----------- source/app/blueprints/alerts/templates/alerts.html | 4 ++++ source/app/datamgmt/alerts/alerts_db.py | 4 ++++ 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/source/app/blueprints/alerts/alerts_routes.py b/source/app/blueprints/alerts/alerts_routes.py index cbc743ed9..c3e609a56 100644 --- a/source/app/blueprints/alerts/alerts_routes.py +++ b/source/app/blueprints/alerts/alerts_routes.py @@ -15,8 +15,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -import tracemalloc - import json import marshmallow from datetime import datetime @@ -118,6 +116,7 @@ def alerts_list_route() -> Response: end_date=request.args.get('creation_end_date'), source_start_date=request.args.get('source_start_date'), source_end_date=request.args.get('source_end_date'), + source_reference=request.args.get('source_reference'), title=request.args.get('alert_title'), description=request.args.get('alert_description'), status=request.args.get('alert_status_id', type=int), @@ -137,9 +136,6 @@ def alerts_list_route() -> Response: resolution_status=request.args.get('alert_resolution_id', type=int), current_user_id=current_user.id ) - import gc - gc.collect() - snapshot_before_selectinload = tracemalloc.take_snapshot() if filtered_data is None: return response_error('Filtering error') @@ -152,12 +148,6 @@ def alerts_list_route() -> Response: 'next_page': filtered_data.next_num if filtered_data.has_next else None, } - snapshot_after_selectinload = tracemalloc.take_snapshot() - top_stats = snapshot_after_selectinload.compare_to(snapshot_before_selectinload, 'lineno') - print("[ Top 10 differences ]") - for stat in top_stats[:10]: - print(stat) - return response_success(data=alerts) diff --git a/source/app/blueprints/alerts/templates/alerts.html b/source/app/blueprints/alerts/templates/alerts.html index c2a4d6cbe..48f4a7024 100644 --- a/source/app/blueprints/alerts/templates/alerts.html +++ b/source/app/blueprints/alerts/templates/alerts.html @@ -158,6 +158,10 @@ +
+ + +
diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index c6d777b17..8e1a823c1 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -56,6 +56,7 @@ def get_filtered_alerts( end_date: str = None, source_start_date: str = None, source_end_date: str = None, + source_reference: str = None, title: str = None, description: str = None, status: int = None, @@ -133,6 +134,9 @@ def get_filtered_alerts( if resolution_status is not None: conditions.append(Alert.alert_resolution_status_id == resolution_status) + if source_reference is not None: + conditions.append(Alert.alert_source_ref.like(f'%{source_reference}%')) + if owner is not None: if owner == -1: conditions.append(Alert.alert_owner_id.is_(None)) From 966d01d986a2dbe4a1513729af9b540c530c9c79 Mon Sep 17 00:00:00 2001 From: whikernel Date: Mon, 28 Oct 2024 15:00:43 +0100 Subject: [PATCH 06/26] [ADD] Alerts indexes --- source/app/alembic/alembic_utils.py | 12 +- .../d5a720d1b99b_add_alerts_indexes.py | 64 ++++++++++ source/app/blueprints/alerts/alerts_routes.py | 23 ++-- source/app/datamgmt/alerts/alerts_db.py | 115 ++++++++++++++++-- source/app/models/alerts.py | 18 ++- source/app/schema/marshables.py | 1 + 6 files changed, 213 insertions(+), 20 deletions(-) create mode 100644 source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py diff --git a/source/app/alembic/alembic_utils.py b/source/app/alembic/alembic_utils.py index f1f2e9bda..285ae664d 100644 --- a/source/app/alembic/alembic_utils.py +++ b/source/app/alembic/alembic_utils.py @@ -30,4 +30,14 @@ def _has_table(table_name): ) inspector = reflection.Inspector.from_engine(engine) tables = inspector.get_table_names() - return table_name in tables \ No newline at end of file + return table_name in tables + + +def index_exists(table_name, index_name): + config = op.get_context().config + engine = engine_from_config( + config.get_section(config.config_ini_section), prefix="sqlalchemy." + ) + inspector = reflection.Inspector.from_engine(engine) + indexes = inspector.get_indexes(table_name) + return any(index['name'] == index_name for index in indexes) \ No newline at end of file diff --git a/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py b/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py new file mode 100644 index 000000000..06bb7ad02 --- /dev/null +++ b/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py @@ -0,0 +1,64 @@ +"""Add alerts indexes + +Revision ID: d5a720d1b99b +Revises: 11aa5b725b8e +Create Date: 2024-10-28 12:54:22.782313 + +""" +import sqlalchemy as sa +from alembic import op +from sqlalchemy import and_, or_, text +from sqlalchemy.orm import Session + +from app.alembic.alembic_utils import _has_table, index_exists + +# revision identifiers, used by Alembic. +revision = 'd5a720d1b99b' +down_revision = '11aa5b725b8e' +branch_labels = None +depends_on = None + + +def upgrade(): + # Adding indexes to the Alerts table + if _has_table('alerts'): + if not index_exists('alerts', 'idx_alerts_title'): + op.create_index('idx_alerts_title', 'alerts', ['alert_title']) + if not index_exists('alerts', 'idx_alerts_creation_time'): + op.create_index('idx_alerts_creation_time', 'alerts', ['alert_creation_time']) + if not index_exists('alerts', 'idx_alerts_source_event_time'): + op.create_index('idx_alerts_source_event_time', 'alerts', ['alert_source_event_time']) + if not index_exists('alerts', 'idx_alerts_customer_id'): + op.create_index('idx_alerts_customer_id', 'alerts', ['alert_customer_id']) + + # Adding indexes to the Ioc table + if _has_table('ioc'): + if not index_exists('ioc', 'idx_ioc_value_hash'): + # Create an index on the MD5 hash of ioc_value to handle large values + op.execute(text("CREATE INDEX idx_ioc_value_hash ON ioc (md5(ioc_value::text))")) + if not index_exists('ioc', 'idx_ioc_tags'): + op.create_index('idx_ioc_tags', 'ioc', ['ioc_tags']) + + # Adding indexes to the CaseAssets table + if _has_table('case_assets'): + if not index_exists('case_assets', 'idx_case_assets_name'): + op.create_index('idx_case_assets_name', 'case_assets', ['asset_name']) + if not index_exists('case_assets', 'idx_case_assets_case_id'): + op.create_index('idx_case_assets_case_id', 'case_assets', ['case_id']) + if not index_exists('case_assets', 'idx_case_assets_date_added'): + op.create_index('idx_case_assets_date_added', 'case_assets', ['date_added']) + if not index_exists('case_assets', 'idx_case_assets_date_update'): + op.create_index('idx_case_assets_date_update', 'case_assets', ['date_update']) + + +def downgrade(): + # Drop indexes + op.drop_index('ix_alert_similarity_alert_id', table_name='alert_similarity') + op.drop_index('ix_alert_similarity_similar_alert_id', table_name='alert_similarity') + op.drop_index('ix_alert_similarity_matching_asset_id', table_name='alert_similarity') + op.drop_index('ix_alert_similarity_matching_ioc_id', table_name='alert_similarity') + op.drop_index('ix_alert_similarity_similarity_type', table_name='alert_similarity') + + # Drop AlertSimilarity table + op.drop_table('alert_similarity') + diff --git a/source/app/blueprints/alerts/alerts_routes.py b/source/app/blueprints/alerts/alerts_routes.py index c3e609a56..938f51eca 100644 --- a/source/app/blueprints/alerts/alerts_routes.py +++ b/source/app/blueprints/alerts/alerts_routes.py @@ -27,7 +27,8 @@ import app from app import db from app.blueprints.case.case_comments import case_comment_update -from app.datamgmt.alerts.alerts_db import get_filtered_alerts, get_alert_by_id, create_case_from_alert +from app.datamgmt.alerts.alerts_db import get_filtered_alerts, get_alert_by_id, create_case_from_alert, \ + register_related_alerts from app.datamgmt.alerts.alerts_db import merge_alert_in_case, unmerge_alert_from_case, cache_similar_alert from app.datamgmt.alerts.alerts_db import get_related_alerts, get_related_alerts_details from app.datamgmt.alerts.alerts_db import get_alert_comments, delete_alert_comment, get_alert_comment @@ -38,7 +39,7 @@ from app.iris_engine.access_control.utils import ac_set_new_case_access from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity -from app.models.alerts import AlertStatus +from app.models.alerts import AlertStatus, AlertSimilarity, Alert from app.models.authorization import Permissions from app.schema.marshables import AlertSchema, CaseSchema, CommentSchema, CaseAssetsSchema, IocSchema from app.util import ac_api_requires @@ -140,15 +141,15 @@ def alerts_list_route() -> Response: if filtered_data is None: return response_error('Filtering error') - alerts = { - 'total': filtered_data.total, - 'alerts': alert_schema.dump(filtered_data.items, many=True), - 'last_page': filtered_data.pages, - 'current_page': filtered_data.page, - 'next_page': filtered_data.next_num if filtered_data.has_next else None, - } + # alerts = { + # 'total': filtered_data.total, + # 'alerts': alert_schema.dump(filtered_data.items, many=True), + # 'last_page': filtered_data.pages, + # 'current_page': filtered_data.page, + # 'next_page': filtered_data.next_num if filtered_data.has_next else None, + # } - return response_success(data=alerts) + return response_success(data=filtered_data) @alerts_blueprint.route('/alerts/add', methods=['POST']) @@ -204,6 +205,8 @@ def alerts_add_route() -> Response: cache_similar_alert(new_alert.alert_customer_id, assets=assets_list, iocs=iocs_list, alert_id=new_alert.alert_id, creation_date=new_alert.alert_source_event_time) + + register_related_alerts(new_alert, assets_list=assets, iocs_list=iocs) new_alert = call_modules_hook('on_postload_alert_create', data=new_alert) diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index 8e1a823c1..440b7ce6a 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -15,6 +15,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +from collections import defaultdict + from copy import deepcopy import json @@ -39,8 +41,9 @@ from app.iris_engine.utils.common import parse_bf_date_format from app.models import Cases, EventCategory, Tags, AssetsType, Comments, CaseAssets, alert_assets_association, \ alert_iocs_association, Ioc, IocLink -from app.models.alerts import Alert, AlertStatus, AlertCaseAssociation, SimilarAlertsCache, AlertResolutionStatus -from app.schema.marshables import EventSchema +from app.models.alerts import Alert, AlertStatus, AlertCaseAssociation, SimilarAlertsCache, AlertResolutionStatus, \ + AlertSimilarity +from app.schema.marshables import EventSchema, AlertSchema from app.util import add_obj_history_entry @@ -74,7 +77,8 @@ def get_filtered_alerts( page: int = 1, per_page: int = 10, sort: str = 'desc', - current_user_id: int = None + current_user_id: int = None, + stack_similar: bool = False ): """ Get a list of alerts that match the given filter conditions @@ -100,9 +104,10 @@ def get_filtered_alerts( per_page (int): The number of alerts per page sort (str): The sort order current_user_id (int): The ID of the current user + stack_similar (bool): Whether to stack similar alerts based on title, assets, and source returns: - list: A list of alerts that match the given filter conditions + dict: A dictionary containing the total count, alerts, and pagination information """ # Build the filter conditions conditions = [] @@ -180,8 +185,9 @@ def get_filtered_alerts( order_func = desc if sort == "desc" else asc - try: + alert_schema = AlertSchema() + try: # Query the alerts using the filter conditions filtered_alerts = db.session.query( Alert @@ -194,11 +200,53 @@ def get_filtered_alerts( order_func(Alert.alert_source_event_time) ).paginate(page=page, per_page=per_page, error_out=False) + alert_ids = [alert.alert_id for alert in filtered_alerts.items] + + # Batch query the AlertSimilarity table for all relevant alerts with specific conditions + similar_alerts = db.session.query(AlertSimilarity).filter( + AlertSimilarity.alert_id.in_(alert_ids), + or_( + and_(AlertSimilarity.similarity_type == 'title_match', AlertSimilarity.matching_asset_id.isnot(None)), + and_(AlertSimilarity.similarity_type == 'title_match', AlertSimilarity.matching_ioc_id.isnot(None)), + and_(AlertSimilarity.matching_asset_id.isnot(None), AlertSimilarity.matching_ioc_id.isnot(None)) + ) + ).all() + + # Group similar alerts by alert_id for easier processing + similarity_map = defaultdict(list) + for similar_alert in similar_alerts: + similarity_map[similar_alert.alert_id].append({ + 'alert_id': similar_alert.similar_alert_id, + 'similarity_type': similar_alert.similarity_type, + 'matching_asset_id': similar_alert.matching_asset_id, + 'matching_ioc_id': similar_alert.matching_ioc_id + }) + + # Attach aggregated alerts to the alert objects + alerts_dict = [] + processed_alerts = set() + + for alert in filtered_alerts.items: + if alert.alert_id in processed_alerts: + continue + + alert.aggregated_alerts = similarity_map.get(alert.alert_id, []) + alerts_dict.append(alert) + processed_alerts.add(alert.alert_id) + for agg in alert.aggregated_alerts: + processed_alerts.add(agg['alert_id']) + + return { + 'total': filtered_alerts.total, + 'alerts': alert_schema.dump(filtered_alerts, many=True), + 'last_page': filtered_alerts.pages, + 'current_page': filtered_alerts.page, + 'next_page': filtered_alerts.next_num if filtered_alerts.has_next else None, + } + except Exception as e: app.app.logger.exception(f"Error getting alerts: {str(e)}") - filtered_alerts = None - - return filtered_alerts + return None def add_alert( @@ -788,6 +836,57 @@ def cache_similar_alert(customer_id, assets, iocs, alert_id, creation_date): db.session.commit() +def register_related_alerts(new_alert=None, assets_list=None, iocs_list=None): + """ + Register related alerts + """ + + + # Step 1: Identify similar alerts based on title, assets, and IOCs + similar_alerts = db.session.query(Alert).filter( + Alert.alert_customer_id == new_alert.alert_customer_id, + Alert.alert_id != new_alert.alert_id, + or_( + Alert.alert_title == new_alert.alert_title, + Alert.assets.any(CaseAssets.asset_name.in_([asset.asset_name for asset in new_alert.assets])), + Alert.iocs.any(Ioc.ioc_value.in_([ioc.ioc_value for ioc in new_alert.iocs])) + ) + ).all() + + # Step 2: Create relationships in the AlertSimilarity table + for similar_alert in similar_alerts: + # Matching on title + if new_alert.alert_title == similar_alert.alert_title: + alert_similarity = AlertSimilarity( + alert_id=new_alert.alert_id, + similar_alert_id=similar_alert.alert_id, + similarity_type="title_match" + ) + db.session.add(alert_similarity) + + # Matching on assets + for asset in new_alert.assets: + if asset in similar_alert.assets: + alert_similarity = AlertSimilarity( + alert_id=new_alert.alert_id, + similar_alert_id=similar_alert.alert_id, + similarity_type="asset_match", + matching_asset_id=asset.asset_id + ) + db.session.add(alert_similarity) + + # Matching on IOCs + for ioc in new_alert.iocs: + if ioc in similar_alert.iocs: + alert_similarity = AlertSimilarity( + alert_id=new_alert.alert_id, + similar_alert_id=similar_alert.alert_id, + similarity_type="ioc_match", + matching_ioc_id=ioc.ioc_id + ) + db.session.add(alert_similarity) + + def delete_similar_alert_cache(alert_id): """ Delete the similar alert cache diff --git a/source/app/models/alerts.py b/source/app/models/alerts.py index dffae3436..61cccd2e2 100644 --- a/source/app/models/alerts.py +++ b/source/app/models/alerts.py @@ -2,7 +2,7 @@ import uuid from sqlalchemy.dialects.postgresql import JSON -from sqlalchemy import BigInteger, Table, Boolean +from sqlalchemy import BigInteger, Table, Boolean, String from sqlalchemy import Column from sqlalchemy import DateTime from sqlalchemy import ForeignKey @@ -114,3 +114,19 @@ def __init__(self, customer_id, alert_id, asset_name=None, ioc_value=None, asset self.asset_type_id = asset_type_id self.ioc_type_id = ioc_type_id self.created_at = created_at if created_at else datetime.utcnow() + + +class AlertSimilarity(db.Model): + __tablename__ = 'alert_similarity' + + id = Column(BigInteger, primary_key=True) + alert_id = Column(BigInteger, ForeignKey('alerts.alert_id'), nullable=False) + similar_alert_id = Column(BigInteger, ForeignKey('alerts.alert_id'), nullable=False) + similarity_type = Column(String(255), nullable=True) + matching_asset_id = Column(BigInteger, ForeignKey('case_assets.asset_id'), nullable=True) + matching_ioc_id = Column(BigInteger, ForeignKey('ioc.ioc_id'), nullable=True) + + alert = relationship("Alert", foreign_keys=[alert_id]) + similar_alert = relationship("Alert", foreign_keys=[similar_alert_id]) + matching_asset = relationship("CaseAssets") + matching_ioc = relationship("Ioc") diff --git a/source/app/schema/marshables.py b/source/app/schema/marshables.py index 2ec669f95..5edbd5e85 100644 --- a/source/app/schema/marshables.py +++ b/source/app/schema/marshables.py @@ -2256,6 +2256,7 @@ class AlertSchema(ma.SQLAlchemyAutoSchema): iocs = ma.Nested(IocSchema, many=True) assets = ma.Nested(CaseAssetsSchema, many=True) resolution_status = ma.Nested(AlertResolutionSchema) + aggregated_alerts = ma.Field() class Meta: model = Alert From bb5c8cc44bb939bfe1b2ce7a847cb6c32e082e07 Mon Sep 17 00:00:00 2001 From: whikernel Date: Tue, 29 Oct 2024 08:51:14 +0100 Subject: [PATCH 07/26] [ADD] Added index for alert source ref --- source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py b/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py index 06bb7ad02..6fe3951d4 100644 --- a/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py +++ b/source/app/alembic/versions/d5a720d1b99b_add_alerts_indexes.py @@ -30,6 +30,8 @@ def upgrade(): op.create_index('idx_alerts_source_event_time', 'alerts', ['alert_source_event_time']) if not index_exists('alerts', 'idx_alerts_customer_id'): op.create_index('idx_alerts_customer_id', 'alerts', ['alert_customer_id']) + if not index_exists('alerts', 'alert_source_ref'): + op.create_index('idx_alert_source_ref', 'alerts', ['alert_source_ref']) # Adding indexes to the Ioc table if _has_table('ioc'): From 4e6e4d5ac58da980a59ea8be6775c6ab9e1cd7eb Mon Sep 17 00:00:00 2001 From: whikernel Date: Tue, 29 Oct 2024 08:54:40 +0100 Subject: [PATCH 08/26] [ADD] Possibility to unassigne alert from API --- source/app/blueprints/alerts/alerts_routes.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/source/app/blueprints/alerts/alerts_routes.py b/source/app/blueprints/alerts/alerts_routes.py index 938f51eca..db1489a31 100644 --- a/source/app/blueprints/alerts/alerts_routes.py +++ b/source/app/blueprints/alerts/alerts_routes.py @@ -110,8 +110,6 @@ def alerts_list_route() -> Response: except ValueError: return response_error('Invalid alert ioc') - alert_schema = AlertSchema() - filtered_data = get_filtered_alerts( start_date=request.args.get('creation_start_date'), end_date=request.args.get('creation_end_date'), @@ -141,14 +139,6 @@ def alerts_list_route() -> Response: if filtered_data is None: return response_error('Filtering error') - # alerts = { - # 'total': filtered_data.total, - # 'alerts': alert_schema.dump(filtered_data.items, many=True), - # 'last_page': filtered_data.pages, - # 'current_page': filtered_data.page, - # 'next_page': filtered_data.next_num if filtered_data.has_next else None, - # } - return response_success(data=filtered_data) @@ -364,6 +354,9 @@ def alerts_update_route(alert_id) -> Response: if data.get('alert_owner_id') is None and updated_alert.alert_owner_id is None: updated_alert.alert_owner_id = current_user.id + if data.get('alert_owner_id') == "-1": + updated_alert.alert_owner_id = None + # Save the changes db.session.commit() From 72a7f3188b4f48f25d747730d6063c6618364f80 Mon Sep 17 00:00:00 2001 From: whikernel Date: Tue, 29 Oct 2024 09:24:33 +0100 Subject: [PATCH 09/26] [UPD] Updated IRIS webhook module --- .../iris_webhooks_module-1.0.7-py3-none-any.whl | Bin 10948 -> 0 bytes .../iris_webhooks_module-1.0.8-py3-none-any.whl | Bin 0 -> 11079 bytes source/requirements.txt | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 source/dependencies/iris_webhooks_module-1.0.7-py3-none-any.whl create mode 100644 source/dependencies/iris_webhooks_module-1.0.8-py3-none-any.whl diff --git a/source/dependencies/iris_webhooks_module-1.0.7-py3-none-any.whl b/source/dependencies/iris_webhooks_module-1.0.7-py3-none-any.whl deleted file mode 100644 index 87914c4970f2e2afd8afc8328deaefdd51854eb2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10948 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 literal 0 HcmV?d00001 diff --git a/source/requirements.txt b/source/requirements.txt index a29374dfc..8a70664dd 100644 --- a/source/requirements.txt +++ b/source/requirements.txt @@ -48,6 +48,6 @@ dependencies/iris_evtx-1.2.0-py3-none-any.whl dependencies/iris_check_module-1.0.1-py3-none-any.whl dependencies/iris_vt_module-1.2.1-py3-none-any.whl dependencies/iris_misp_module-1.3.0-py3-none-any.whl -dependencies/iris_webhooks_module-1.0.7-py3-none-any.whl +dependencies/iris_webhooks_module-1.0.8-py3-none-any.whl dependencies/iris_intelowl_module-0.1.0-py3-none-any.whl dependencies/iris_seika_module-1.0.0-py3-none-any.whl \ No newline at end of file From 0933a3c6b40083be12c792e6fb9e4e4a4d005ec2 Mon Sep 17 00:00:00 2001 From: whikernel Date: Tue, 29 Oct 2024 10:56:44 +0100 Subject: [PATCH 10/26] [ADD] Alerts graph zoom --- .../app/blueprints/alerts/templates/alerts.html | 2 ++ .../static/assets/css/img/network/downArrow.png | Bin 0 -> 4460 bytes .../static/assets/css/img/network/leftArrow.png | Bin 0 -> 4531 bytes .../app/static/assets/css/img/network/minus.png | Bin 0 -> 4147 bytes .../app/static/assets/css/img/network/plus.png | Bin 0 -> 4341 bytes .../static/assets/css/img/network/rightArrow.png | Bin 0 -> 4514 bytes .../static/assets/css/img/network/upArrow.png | Bin 0 -> 4461 bytes .../assets/css/img/network/zoomExtends.png | Bin 0 -> 4464 bytes source/app/static/assets/js/iris/alerts.js | 7 ++++++- 9 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 source/app/static/assets/css/img/network/downArrow.png create mode 100644 source/app/static/assets/css/img/network/leftArrow.png create mode 100644 source/app/static/assets/css/img/network/minus.png create mode 100644 source/app/static/assets/css/img/network/plus.png create mode 100644 source/app/static/assets/css/img/network/rightArrow.png create mode 100644 source/app/static/assets/css/img/network/upArrow.png create mode 100644 source/app/static/assets/css/img/network/zoomExtends.png diff --git a/source/app/blueprints/alerts/templates/alerts.html b/source/app/blueprints/alerts/templates/alerts.html index 48f4a7024..a7c28c1ac 100644 --- a/source/app/blueprints/alerts/templates/alerts.html +++ b/source/app/blueprints/alerts/templates/alerts.html @@ -5,6 +5,8 @@ {% block stylesheets %} + + {% endblock stylesheets %} diff --git a/source/app/static/assets/css/img/network/downArrow.png b/source/app/static/assets/css/img/network/downArrow.png new file mode 100644 index 0000000000000000000000000000000000000000..e77d5e6d4157b12a5b2fa08a9fc138f0ab9eb4e7 GIT binary patch literal 4460 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�EM>-Bgb&Rym2{we-b7Oawc(c4k4MzLw~<#m(JZ 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 Re)#|Z002ovPDHLkV1iMdkGuc? literal 0 HcmV?d00001 diff --git a/source/app/static/assets/css/img/network/minus.png b/source/app/static/assets/css/img/network/minus.png new file mode 100644 index 0000000000000000000000000000000000000000..30698076b9ac59f0880752ddc13af3f6bf062403 GIT binary patch literal 4147 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 literal 0 HcmV?d00001 diff --git a/source/app/static/assets/css/img/network/plus.png b/source/app/static/assets/css/img/network/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..f7ab2a334e566b6deb9657d7d8370f591bfb81bc GIT binary patch literal 4341 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 literal 0 HcmV?d00001 diff --git a/source/app/static/assets/css/img/network/rightArrow.png b/source/app/static/assets/css/img/network/rightArrow.png new file mode 100644 index 0000000000000000000000000000000000000000..c3a209d8b0a58355305aae03b5520a1e070d8eab GIT binary patch literal 4514 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 literal 0 HcmV?d00001 diff --git a/source/app/static/assets/css/img/network/zoomExtends.png b/source/app/static/assets/css/img/network/zoomExtends.png new file mode 100644 index 0000000000000000000000000000000000000000..74595c6358448fbdcfbd62629084841ca7e8b8a9 GIT binary patch literal 4464 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 Date: Tue, 29 Oct 2024 16:43:14 +0100 Subject: [PATCH 11/26] [ADD] Possibility to check alerts --- .../app/blueprints/case/case_assets_routes.py | 44 ++++++++++++- source/app/datamgmt/alerts/alerts_db.py | 66 +------------------ source/app/datamgmt/case/case_assets_db.py | 8 +++ source/app/schema/marshables.py | 3 +- .../app/static/assets/js/iris/case.asset.js | 60 +++++++++++------ source/app/static/assets/js/iris/common.js | 56 +++++++++------- 6 files changed, 126 insertions(+), 111 deletions(-) diff --git a/source/app/blueprints/case/case_assets_routes.py b/source/app/blueprints/case/case_assets_routes.py index f3761c1ef..e7409e113 100644 --- a/source/app/blueprints/case/case_assets_routes.py +++ b/source/app/blueprints/case/case_assets_routes.py @@ -31,7 +31,7 @@ import app from app import db from app.blueprints.case.case_comments import case_comment_update -from app.datamgmt.case.case_assets_db import add_comment_to_asset +from app.datamgmt.case.case_assets_db import add_comment_to_asset, get_raw_assets from app.datamgmt.case.case_assets_db import create_asset from app.datamgmt.case.case_assets_db import delete_asset from app.datamgmt.case.case_assets_db import delete_asset_comment @@ -94,6 +94,47 @@ def case_assets(caseid, url_redir): return render_template("case_assets.html", case=case, form=form) +@case_assets_blueprint.route('/case/assets/filter', methods=['GET']) +@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access) +def case_filter_assets(caseid): + """ + Returns the list of assets from the case. + :return: A JSON object containing the assets of the case, enhanced with assets seen on other cases. + """ + + # Get all assets objects from the case and the customer id + ret = {} + assets = CaseAssetsSchema().dump(get_raw_assets(caseid), many=True) + customer_id = get_case_client_id(caseid) + + ioc_links_req = get_assets_ioc_links(caseid) + + cache_ioc_link = {} + for ioc in ioc_links_req: + + if ioc.asset_id not in cache_ioc_link: + cache_ioc_link[ioc.asset_id] = [ioc._asdict()] + else: + cache_ioc_link[ioc.asset_id].append(ioc._asdict()) + + cases_access = get_user_cases_fast(current_user.id) + + for a in assets: + a['ioc_links'] = cache_ioc_link.get(a['asset_id']) + + if len(assets) < 300: + # Find similar assets from other cases with the same customer + a['link'] = list(get_similar_assets( + a['asset_name'], a['asset_type_id'], caseid, customer_id, cases_access)) + else: + a['link'] = [] + + ret['assets'] = assets + + ret['state'] = get_assets_state(caseid=caseid) + + return response_success("", data=ret) + @case_assets_blueprint.route('/case/assets/list', methods=['GET']) @ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access) def case_list_assets(caseid): @@ -102,7 +143,6 @@ def case_list_assets(caseid): :return: A JSON object containing the assets of the case, enhanced with assets seen on other cases. """ - # Get all assets objects from the case and the customer id assets = get_assets(caseid) customer_id = get_case_client_id(caseid) diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index 440b7ce6a..b7a12ba3f 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -15,8 +15,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -from collections import defaultdict - from copy import deepcopy import json @@ -26,7 +24,7 @@ from operator import and_ from sqlalchemy import desc, asc, func, tuple_, or_ from sqlalchemy.orm import aliased, make_transient, selectinload -from typing import List, Tuple +from typing import List, Tuple, Dict import app from app import db @@ -77,35 +75,11 @@ def get_filtered_alerts( page: int = 1, per_page: int = 10, sort: str = 'desc', - current_user_id: int = None, - stack_similar: bool = False + current_user_id: int = None ): """ Get a list of alerts that match the given filter conditions - args: - start_date (datetime): The start date of the alert creation time - end_date (datetime): The end date of the alert creation time - title (str): The title of the alert - description (str): The description of the alert - status (str): The status of the alert - severity (str): The severity of the alert - owner (str): The owner of the alert - source (str): The source of the alert - tags (str): The tags of the alert - case_id (int): The case id of the alert - client (int): The client id of the alert - classification (int): The classification id of the alert - alert_ids (int): The alert ids - assets (list): The assets of the alert - iocs (list): The iocs of the alert - resolution_status (int): The resolution status of the alert - page (int): The page number - per_page (int): The number of alerts per page - sort (str): The sort order - current_user_id (int): The ID of the current user - stack_similar (bool): Whether to stack similar alerts based on title, assets, and source - returns: dict: A dictionary containing the total count, alerts, and pagination information """ @@ -200,42 +174,6 @@ def get_filtered_alerts( order_func(Alert.alert_source_event_time) ).paginate(page=page, per_page=per_page, error_out=False) - alert_ids = [alert.alert_id for alert in filtered_alerts.items] - - # Batch query the AlertSimilarity table for all relevant alerts with specific conditions - similar_alerts = db.session.query(AlertSimilarity).filter( - AlertSimilarity.alert_id.in_(alert_ids), - or_( - and_(AlertSimilarity.similarity_type == 'title_match', AlertSimilarity.matching_asset_id.isnot(None)), - and_(AlertSimilarity.similarity_type == 'title_match', AlertSimilarity.matching_ioc_id.isnot(None)), - and_(AlertSimilarity.matching_asset_id.isnot(None), AlertSimilarity.matching_ioc_id.isnot(None)) - ) - ).all() - - # Group similar alerts by alert_id for easier processing - similarity_map = defaultdict(list) - for similar_alert in similar_alerts: - similarity_map[similar_alert.alert_id].append({ - 'alert_id': similar_alert.similar_alert_id, - 'similarity_type': similar_alert.similarity_type, - 'matching_asset_id': similar_alert.matching_asset_id, - 'matching_ioc_id': similar_alert.matching_ioc_id - }) - - # Attach aggregated alerts to the alert objects - alerts_dict = [] - processed_alerts = set() - - for alert in filtered_alerts.items: - if alert.alert_id in processed_alerts: - continue - - alert.aggregated_alerts = similarity_map.get(alert.alert_id, []) - alerts_dict.append(alert) - processed_alerts.add(alert.alert_id) - for agg in alert.aggregated_alerts: - processed_alerts.add(agg['alert_id']) - return { 'total': filtered_alerts.total, 'alerts': alert_schema.dump(filtered_alerts, many=True), diff --git a/source/app/datamgmt/case/case_assets_db.py b/source/app/datamgmt/case/case_assets_db.py index a7ae7eec9..f6bc5d0e9 100644 --- a/source/app/datamgmt/case/case_assets_db.py +++ b/source/app/datamgmt/case/case_assets_db.py @@ -83,6 +83,14 @@ def get_assets(caseid): return assets +def get_raw_assets(caseid): + assets = CaseAssets.query.filter( + CaseAssets.case_id == caseid + ).all() + + return assets + + def get_assets_name(caseid): assets_names = CaseAssets.query.with_entities( CaseAssets.asset_name diff --git a/source/app/schema/marshables.py b/source/app/schema/marshables.py index 5edbd5e85..94eb41190 100644 --- a/source/app/schema/marshables.py +++ b/source/app/schema/marshables.py @@ -666,6 +666,8 @@ class CaseAssetsSchema(ma.SQLAlchemyAutoSchema): ioc_links: List[int] = fields.List(fields.Integer, required=False) asset_enrichment: str = auto_field('asset_enrichment', required=False) asset_type: AssetTypeSchema = ma.Nested(AssetTypeSchema, required=False) + alerts = fields.Nested('AlertSchema', many=True, exclude=['assets']) + analysis_status = fields.Nested('AnalysisStatusSchema', required=False) class Meta: model = CaseAssets @@ -2256,7 +2258,6 @@ class AlertSchema(ma.SQLAlchemyAutoSchema): iocs = ma.Nested(IocSchema, many=True) assets = ma.Nested(CaseAssetsSchema, many=True) resolution_status = ma.Nested(AlertResolutionSchema) - aggregated_alerts = ma.Field() class Meta: model = Alert diff --git a/source/app/static/assets/js/iris/case.asset.js b/source/app/static/assets/js/iris/case.asset.js index e5732cb6b..a455eba15 100644 --- a/source/app/static/assets/js/iris/case.asset.js +++ b/source/app/static/assets/js/iris/case.asset.js @@ -120,7 +120,7 @@ function add_assets() { function get_case_assets() { show_loader(); - get_request_api('/case/assets/list') + get_request_api('/case/assets/filter') .done(function (response) { if (response.status == 'success') { if (response.data != null) { @@ -133,7 +133,17 @@ function get_case_assets() { Table.clear(); Table.rows.add(jsdata.assets); Table.columns.adjust().draw(); - load_menu_mod_options('asset', Table, delete_asset); + load_menu_mod_options('asset', Table, delete_asset, [{ + type: 'option', + title: 'Check Alerts', + multi: false, + iconClass: 'fas fa-bell', + action: function(rows) { + let row = rows[0]; + let asset = get_row_value(row); + window.open(`/alerts?alert_assets=${asset}`, '_blank'); + } + }]); $('[data-toggle="popover"]').popover(); set_last_state(jsdata.state); hide_loader(); @@ -377,32 +387,40 @@ $(document).ready(function(){ if (row.link.length > 0) { let has_compro = false; let datacontent = 'data-content="'; - for (let idx in row.link) { - if (row.link[idx]['asset_compromise_status_id'] === 1) { + + row.link.forEach(link => { + const caseInfo = `Observed `; + const caseLink = `case #${link.case_id} `; + const date = link.case_open_date.replace('00:00:00 GMT', ''); + + if (link.asset_compromise_status_id === 1) { has_compro = true; - datacontent += `Observed as compromised
on case #${row.link[idx]['case_id']} (${row.link[idx]['case_open_date'].replace('00:00:00 GMT', '')}) for the same customer.

`; + datacontent += `${caseInfo} as compromised
on ${caseLink} (${date}) for the same customer.

`; } else { - - datacontent += `Observed as not compromised
on case #${row.link[idx]['case_id']} (${row.link[idx]['case_open_date'].replace('00:00:00 GMT', '')}) for the same customer.

`; + datacontent += `${caseInfo} as not compromised
on ${caseLink} (${date}) for the same customer.

`; } - } - if (has_compro) { - compro += ``; + } + + if (row.alerts.length > 0) { + let alerts_content = ""; + + row.alerts.forEach(alert => { + alerts_content += `#${alert.alert_id} - ${alert.alert_title.replace(/'/g, "'").replace(/"/g, """)}
`; + } ); + alerts_content += `More..`; + - compro += datacontent; - compro += '">'; + compro += ``; } let img = $('') .addClass('mr-2') .css({width: '1.5em', height: '1.5em'}) - .attr('src', '/static/assets/img/graph/' + (row['asset_compromise_status_id'] == 1 ? row['asset_icon_compromised'] : row['asset_icon_not_compromised'])) - .attr('title', row['asset_type']); + .attr('src', '/static/assets/img/graph/' + (row['asset_compromise_status_id'] == 1 ? row['asset_type']['asset_icon_compromised'] : row['asset_type']['asset_icon_not_compromised'])) + .attr('title', row['asset_type']['asset_name']); let link = $('') .attr('href', 'javascript:void(0);') @@ -419,7 +437,7 @@ $(document).ready(function(){ } }, { - "data": "asset_type", + "data": "asset_type.asset_name", "render": function (data, type, row, meta) { if (type === 'display') { data = sanitizeHTML(data);} return data; @@ -483,7 +501,7 @@ $(document).ready(function(){ "data": "analysis_status", "render": function(data, type, row, meta) { if (type === 'display') { - data = sanitizeHTML(data); + data = sanitizeHTML(data['name']); if (data == 'To be done') { flag = 'danger'; } else if (data == 'Started') { diff --git a/source/app/static/assets/js/iris/common.js b/source/app/static/assets/js/iris/common.js index cd51aa1c6..4501ef601 100644 --- a/source/app/static/assets/js/iris/common.js +++ b/source/app/static/assets/js/iris/common.js @@ -1081,7 +1081,7 @@ function goto_case_number() { } -function load_menu_mod_options(data_type, table, deletion_fn) { +function load_menu_mod_options(data_type, table, deletion_fn, additionalOptions = []) { var actionOptions = { classes: [], contextMenu: { @@ -1105,28 +1105,28 @@ function load_menu_mod_options(data_type, table, deletion_fn) { items: [], }; - datatype_map = { + let datatype_map = { 'task': 'tasks', 'ioc': 'ioc', 'evidence': 'evidences', 'note': 'notes', 'asset': 'assets', 'event': 'timeline/events' - } + }; - get_request_api("/dim/hooks/options/"+ data_type +"/list") + get_request_api("/dim/hooks/options/" + data_type + "/list") .done((data) => { - if(notify_auto_api(data, true)) { + if (notify_auto_api(data, true)) { if (data.data != null) { - jsdata = data.data; + let jsdata = data.data; actionOptions.items.push({ type: 'option', title: 'Share', multi: false, iconClass: 'fas fa-share', - action: function(rows){ - row = rows[0]; + action: function(rows) { + let row = rows[0]; copy_object_link(get_row_id(row)); } }); @@ -1136,8 +1136,8 @@ function load_menu_mod_options(data_type, table, deletion_fn) { title: 'Comment', multi: false, iconClass: 'fas fa-comments', - action: function(rows){ - row = rows[0]; + action: function(rows) { + let row = rows[0]; if (data_type in datatype_map) { comment_element(get_row_id(row), datatype_map[data_type]); } @@ -1149,8 +1149,8 @@ function load_menu_mod_options(data_type, table, deletion_fn) { title: 'Markdown Link', multi: false, iconClass: 'fa-brands fa-markdown', - action: function(rows){ - row = rows[0]; + action: function(rows) { + let row = rows[0]; copy_object_link_md(data_type, get_row_id(row)); } }); @@ -1160,19 +1160,22 @@ function load_menu_mod_options(data_type, table, deletion_fn) { title: 'Copy', multi: false, iconClass: 'fa-regular fa-copy', - action: function(rows){ - row = rows[0]; + action: function(rows) { + let row = rows[0]; copy_text_clipboard(get_row_value(row)); } }); + additionalOptions.forEach(option => { + actionOptions.items.push(option); + }); + actionOptions.items.push({ type: 'divider' }); - jdata_menu_options = jsdata; - for (option in jsdata) { - opt = jsdata[option]; + for (let option in jsdata) { + let opt = jsdata[option]; actionOptions.items.push({ type: 'option', @@ -1181,10 +1184,10 @@ function load_menu_mod_options(data_type, table, deletion_fn) { multiTitle: opt.manual_hook_ui_name, iconClass: 'fas fa-rocket', contextMenuClasses: ['text-dark'], - action: function (rows, de, ke) { + action: function(rows, de, ke) { init_module_processing_wrap(rows, data_type, de[0].outerText); }, - }) + }); } if (deletion_fn !== undefined) { @@ -1198,21 +1201,22 @@ function load_menu_mod_options(data_type, table, deletion_fn) { multi: false, iconClass: 'fas fa-trash', contextMenuClasses: ['text-danger'], - action: function(rows){ - row = rows[0]; + action: function(rows) { + let row = rows[0]; deletion_fn(get_row_id(row)); } }); } - tableActions = table.contextualActions(actionOptions); + let tableActions = table.contextualActions(actionOptions); tableActions.update(); } } - }) + }); } + function get_custom_attributes_fields() { values = Object(); has_error = []; @@ -1696,6 +1700,12 @@ function do_deletion_prompt(message, force_prompt=false) { } } +function escapeHtml(text) { + let parser = new DOMParser(); + let escapedDoc = parser.parseFromString(text, 'text/html'); + return escapedDoc.documentElement.textContent; +} + function toBinary64(string) { const codeUnits = new Uint16Array(string.length); for (let i = 0; i < codeUnits.length; i++) { From 0fba01dca5e82ea06f14fa01ce5f9026feb205b9 Mon Sep 17 00:00:00 2001 From: whikernel Date: Tue, 29 Oct 2024 20:43:33 +0100 Subject: [PATCH 12/26] =?UTF-8?q?Bump=20version:=202.4.14=20=E2=86=92=202.?= =?UTF-8?q?4.15-beta?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- README.md | 4 ++-- docker-compose.yml | 8 ++++---- source/app/configuration.py | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 3968d993f..475da0f05 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2.4.14 +current_version = 2.4.15-beta commit = True tag = True parse = (?P\d+)\.(?P\d+)\.(?P\d+)(-(?P.*)-(?P\d+))? diff --git a/README.md b/README.md index 232abcd46..1e722ba9e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

Incident Response Investigation System
- Current Version v2.4.14 + Current Version v2.4.15
Online Demonstration

@@ -52,7 +52,7 @@ git clone https://github.com/dfir-iris/iris-web.git cd iris-web # Checkout to the last tagged version -git checkout v2.4.14 +git checkout v2.4.15 # Copy the environment file cp .env.model .env diff --git a/docker-compose.yml b/docker-compose.yml index 42917bf03..957c091cc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,25 +25,25 @@ services: extends: file: docker-compose.base.yml service: db - image: ${DB_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_db}:${DB_IMAGE_TAG:-v2.4.14} + image: ${DB_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_db}:${DB_IMAGE_TAG:-v2.4.15} app: extends: file: docker-compose.base.yml service: app - image: ${APP_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_app}:${APP_IMAGE_TAG:-v2.4.14} + image: ${APP_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_app}:${APP_IMAGE_TAG:-v2.4.15} worker: extends: file: docker-compose.base.yml service: worker - image: ${APP_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_app}:${APP_IMAGE_TAG:-v2.4.14} + image: ${APP_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_app}:${APP_IMAGE_TAG:-v2.4.15} nginx: extends: file: docker-compose.base.yml service: nginx - image: ${NGINX_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_nginx}:${NGINX_IMAGE_TAG:-v2.4.14} + image: ${NGINX_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_nginx}:${NGINX_IMAGE_TAG:-v2.4.15} volumes: iris-downloads: diff --git a/source/app/configuration.py b/source/app/configuration.py index 974f55a3c..e4db364f0 100644 --- a/source/app/configuration.py +++ b/source/app/configuration.py @@ -263,7 +263,7 @@ class CeleryConfig: # --------- APP --------- class Config: # Handled by bumpversion - IRIS_VERSION = "v2.4.14" # DO NOT EDIT THIS LINE MANUALLY + IRIS_VERSION = "v2.4.15" # DO NOT EDIT THIS LINE MANUALLY if os.environ.get('IRIS_DEMO_VERSION') is not None and os.environ.get('IRIS_DEMO_VERSION') != 'None': IRIS_VERSION = os.environ.get('IRIS_DEMO_VERSION') From 789551698f5b138c5c8dd7f145c971bc0e68e7da Mon Sep 17 00:00:00 2001 From: whikernel Date: Thu, 31 Oct 2024 11:37:04 +0100 Subject: [PATCH 13/26] [FIX] Deletion of alerts with new relations --- source/app/blueprints/alerts/alerts_routes.py | 5 ++++- source/app/datamgmt/alerts/alerts_db.py | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/source/app/blueprints/alerts/alerts_routes.py b/source/app/blueprints/alerts/alerts_routes.py index db1489a31..dd83d63e2 100644 --- a/source/app/blueprints/alerts/alerts_routes.py +++ b/source/app/blueprints/alerts/alerts_routes.py @@ -28,7 +28,7 @@ from app import db from app.blueprints.case.case_comments import case_comment_update from app.datamgmt.alerts.alerts_db import get_filtered_alerts, get_alert_by_id, create_case_from_alert, \ - register_related_alerts + register_related_alerts, delete_related_alerts_cache from app.datamgmt.alerts.alerts_db import merge_alert_in_case, unmerge_alert_from_case, cache_similar_alert from app.datamgmt.alerts.alerts_db import get_related_alerts, get_related_alerts_details from app.datamgmt.alerts.alerts_db import get_alert_comments, delete_alert_comment, get_alert_comment @@ -527,6 +527,9 @@ def alerts_delete_route(alert_id) -> Response: # Delete the case association delete_similar_alert_cache(alert_id=alert_id) + # Delete the similarity entries + delete_related_alerts_cache(alert_id=alert_id) + # Delete the alert from the database db.session.delete(alert) db.session.commit() diff --git a/source/app/datamgmt/alerts/alerts_db.py b/source/app/datamgmt/alerts/alerts_db.py index b7a12ba3f..8d3745adb 100644 --- a/source/app/datamgmt/alerts/alerts_db.py +++ b/source/app/datamgmt/alerts/alerts_db.py @@ -839,6 +839,24 @@ def delete_similar_alert_cache(alert_id): db.session.commit() +def delete_related_alerts_cache(alert_id): + """ + Delete the related alerts cache + + args: + alert_id (int): The ID of the alert + + returns: + None + """ + AlertSimilarity.query.filter( + or_( + AlertSimilarity.alert_id == alert_id, + AlertSimilarity.similar_alert_id == alert_id + ) + ).delete() + db.session.commit() + def delete_similar_alerts_cache(alert_ids: List[int]): """ Delete the similar alerts cache From e2e026634ac7b53ba2d54fddf4493e38ce438de6 Mon Sep 17 00:00:00 2001 From: whikernel Date: Thu, 31 Oct 2024 11:42:26 +0100 Subject: [PATCH 14/26] =?UTF-8?q?Bump=20version:=202.4.15-beta=20=E2=86=92?= =?UTF-8?q?=202.4.15-rc1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 475da0f05..c02dcea4c 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2.4.15-beta +current_version = 2.4.15-rc1 commit = True tag = True parse = (?P\d+)\.(?P\d+)\.(?P\d+)(-(?P.*)-(?P\d+))? From fd6f8fe077b899ea30f591c95a7e9f3af69cb2d5 Mon Sep 17 00:00:00 2001 From: Vladimir-A <32281993+Vladimir-A@users.noreply.github.com> Date: Sat, 2 Nov 2024 17:43:40 +0300 Subject: [PATCH 15/26] [FIX] report template language --- source/app/datamgmt/case/case_db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/app/datamgmt/case/case_db.py b/source/app/datamgmt/case/case_db.py index 3d9998ba7..7050943a4 100644 --- a/source/app/datamgmt/case/case_db.py +++ b/source/app/datamgmt/case/case_db.py @@ -110,7 +110,7 @@ def get_case_report_template(): Languages.name, CaseTemplateReport.description ).filter(and_( - Languages.id == CaseTemplateReport.report_type_id, + Languages.id == CaseTemplateReport.language_id, ReportType.name == "Investigation" )).join( CaseTemplateReport.report_type From 1e5e8053c851984b6aaa7e751232f27497a659f8 Mon Sep 17 00:00:00 2001 From: whikernel Date: Sat, 9 Nov 2024 18:14:15 +0100 Subject: [PATCH 16/26] [ADD] Better visibility of commented elements --- .../blueprints/case/templates/modal_add_case_asset.html | 2 +- .../blueprints/case/templates/modal_add_case_event.html | 2 +- .../app/blueprints/case/templates/modal_add_case_ioc.html | 2 +- .../blueprints/case/templates/modal_add_case_rfile.html | 2 +- .../blueprints/case/templates/modal_add_case_task.html | 2 +- source/app/blueprints/case/templates/modal_note_edit.html | 2 +- source/app/static/assets/css/atlantis.css | 8 ++++++++ 7 files changed, 14 insertions(+), 6 deletions(-) diff --git a/source/app/blueprints/case/templates/modal_add_case_asset.html b/source/app/blueprints/case/templates/modal_add_case_asset.html index 7b1283899..30f7b353b 100644 --- a/source/app/blueprints/case/templates/modal_add_case_asset.html +++ b/source/app/blueprints/case/templates/modal_add_case_asset.html @@ -20,7 +20,7 @@
{% endif %} diff --git a/source/app/blueprints/case/templates/modal_add_case_event.html b/source/app/blueprints/case/templates/modal_add_case_event.html index f3ecfb92d..2a6d4fa21 100644 --- a/source/app/blueprints/case/templates/modal_add_case_event.html +++ b/source/app/blueprints/case/templates/modal_add_case_event.html @@ -28,7 +28,7 @@ ` : ''} ${alert.alert_source_link ? `
Source Link:
-
${ +
${ alert.alert_source_link && alert.alert_source_link.startsWith('http') - ? `${alert.alert_source_link}` + ? `${alert.alert_source_link} + ` : 'No valid link provided' }
` : ''} ${alert.alert_source_ref ? `
Source Reference:
-
${alert.alert_source_ref}
+
+ ${alert.alert_source_ref} + +
` : ''} ${alert.alert_source_event_time ? `
Source Event Time:
-
${formatTime(alert.alert_source_event_time)} UTC
+
+ ${formatTime(alert.alert_source_event_time)} UTC + +
` : ''} ${alert.alert_creation_time ? `
IRIS Creation Time:
-
${formatTime(alert.alert_creation_time)} UTC
+
+ ${formatTime(alert.alert_creation_time)} UTC + +
` : ''}
@@ -1094,7 +1112,12 @@ function renderAlert(alert, expanded=false, modulesOptionsAlertReq, .map( (ioc) => ` - ${filterXSS(ioc.ioc_value)} + + ${filterXSS(ioc.ioc_value)} + + ${filterXSS(ioc.ioc_description)} ${ioc.ioc_type ? filterXSS(ioc.ioc_type.type_name) : '-'} ${filterXSS(ioc.ioc_tlp) ? ioc.ioc_tlp : '-'} @@ -1146,6 +1169,12 @@ function renderAlert(alert, expanded=false, modulesOptionsAlertReq, .map( (asset) => ` + + ${asset.asset_name ? filterXSS(asset.asset_name) : '-'} + + ${asset.asset_name ? filterXSS(asset.asset_name) : '-'} ${asset.asset_description ? filterXSS(asset.asset_description) : '-'} ${asset.asset_type ? filterXSS(asset.asset_type.asset_name) : '-'} @@ -1417,6 +1446,11 @@ async function updateAlerts(page, per_page, filters = {}, paging=false){ filterString || queryParams.get('filter_id') ? $('#resetFilters').show() : $('#resetFilters').hide(); alertsContainer.show(); + + $('.copy-btn').off().on('click', function() { + let value = $(this).data('value'); + copy_text_clipboard(value); + }); } $('#alertsPerPage').on('change', (e) => { diff --git a/source/app/static/assets/js/iris/common.js b/source/app/static/assets/js/iris/common.js index 4501ef601..befd2ec56 100644 --- a/source/app/static/assets/js/iris/common.js +++ b/source/app/static/assets/js/iris/common.js @@ -626,7 +626,7 @@ function copy_object_link_md(data_type, node_id){ function copy_text_clipboardb(data){ navigator.clipboard.writeText(fromBinary64(data)).then(function() { - notify_success('Copied!'); + notify_success('Copied'); }, function(err) { notify_error('Can\'t copy link. I printed it in console.'); console.error(err); @@ -635,7 +635,7 @@ function copy_text_clipboardb(data){ function copy_text_clipboard(data){ navigator.clipboard.writeText(data).then(function() { - notify_success('Copied!'); + notify_success('Copied'); }, function(err) { notify_error('Can\'t copy link. I printed it in console.'); console.error(err); From 70767b64d0273bddd13ebf7f943d3b5f18e492cf Mon Sep 17 00:00:00 2001 From: whikernel Date: Sat, 9 Nov 2024 22:06:09 +0100 Subject: [PATCH 19/26] =?UTF-8?q?Bump=20version:=202.4.15-rc1=20=E2=86=92?= =?UTF-8?q?=202.4.15-rc2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index c02dcea4c..abcccbd13 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2.4.15-rc1 +current_version = 2.4.15-rc2 commit = True tag = True parse = (?P\d+)\.(?P\d+)\.(?P\d+)(-(?P.*)-(?P\d+))? From 5299bfbe3030ecd8edd16e78b1aecc43c7005efc Mon Sep 17 00:00:00 2001 From: whikernel Date: Sat, 9 Nov 2024 22:09:30 +0100 Subject: [PATCH 20/26] =?UTF-8?q?Bump=20version:=202.4.15-rc2=20=E2=86=92?= =?UTF-8?q?=202.4.15?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index abcccbd13..46c6b672c 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2.4.15-rc2 +current_version = 2.4.15 commit = True tag = True parse = (?P\d+)\.(?P\d+)\.(?P\d+)(-(?P.*)-(?P\d+))? From 50cf190d138a139e1846a3ad919bbe244073b36f Mon Sep 17 00:00:00 2001 From: whikernel Date: Thu, 14 Nov 2024 10:18:44 +0100 Subject: [PATCH 21/26] [FIX] Issue with hybrid OIDC / Local auth --- source/app/blueprints/login/login_routes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/app/blueprints/login/login_routes.py b/source/app/blueprints/login/login_routes.py index 0d184f8ab..1deaef42a 100644 --- a/source/app/blueprints/login/login_routes.py +++ b/source/app/blueprints/login/login_routes.py @@ -253,16 +253,16 @@ def oidc_authorise(): if user and not user.active: return response_error("User not active in IRIS", 403) - return wrap_login_user(user) + return wrap_login_user(user, is_oidc=True) -def wrap_login_user(user): +def wrap_login_user(user, is_oidc=False): session['username'] = user.user if 'SERVER_SETTINGS' not in app.config: app.config['SERVER_SETTINGS'] = get_server_settings_as_dict() - if app.config['SERVER_SETTINGS']['enforce_mfa']: + if app.config['SERVER_SETTINGS']['enforce_mfa'] is True and is_oidc is False: if "mfa_verified" not in session or session["mfa_verified"] is False: return redirect(url_for('mfa_verify')) From 6046f857288d4d1a60d5e5b7b9e36526f17a5828 Mon Sep 17 00:00:00 2001 From: whikernel Date: Thu, 14 Nov 2024 10:19:28 +0100 Subject: [PATCH 22/26] =?UTF-8?q?Bump=20version:=202.4.15=20=E2=86=92=202.?= =?UTF-8?q?4.15?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- docker-compose.yml | 8 ++++---- source/app/configuration.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1e722ba9e..98ce3992a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

Incident Response Investigation System
- Current Version v2.4.15 + Current Version vv2.4.15
Online Demonstration

@@ -52,7 +52,7 @@ git clone https://github.com/dfir-iris/iris-web.git cd iris-web # Checkout to the last tagged version -git checkout v2.4.15 +git checkout vv2.4.15 # Copy the environment file cp .env.model .env diff --git a/docker-compose.yml b/docker-compose.yml index 957c091cc..4cc7ac21a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,25 +25,25 @@ services: extends: file: docker-compose.base.yml service: db - image: ${DB_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_db}:${DB_IMAGE_TAG:-v2.4.15} + image: ${DB_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_db}:${DB_IMAGE_TAG:-v-v2.4.15} app: extends: file: docker-compose.base.yml service: app - image: ${APP_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_app}:${APP_IMAGE_TAG:-v2.4.15} + image: ${APP_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_app}:${APP_IMAGE_TAG:-v-v2.4.15} worker: extends: file: docker-compose.base.yml service: worker - image: ${APP_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_app}:${APP_IMAGE_TAG:-v2.4.15} + image: ${APP_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_app}:${APP_IMAGE_TAG:-v-v2.4.15} nginx: extends: file: docker-compose.base.yml service: nginx - image: ${NGINX_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_nginx}:${NGINX_IMAGE_TAG:-v2.4.15} + image: ${NGINX_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_nginx}:${NGINX_IMAGE_TAG:-v-v2.4.15} volumes: iris-downloads: diff --git a/source/app/configuration.py b/source/app/configuration.py index e4db364f0..a37975327 100644 --- a/source/app/configuration.py +++ b/source/app/configuration.py @@ -263,7 +263,7 @@ class CeleryConfig: # --------- APP --------- class Config: # Handled by bumpversion - IRIS_VERSION = "v2.4.15" # DO NOT EDIT THIS LINE MANUALLY + IRIS_VERSION = "vIRIS_VERSION = "v2.4.15"" # DO NOT EDIT THIS LINE MANUALLY if os.environ.get('IRIS_DEMO_VERSION') is not None and os.environ.get('IRIS_DEMO_VERSION') != 'None': IRIS_VERSION = os.environ.get('IRIS_DEMO_VERSION') From e379781072af98da98443d0f48d4439a39125670 Mon Sep 17 00:00:00 2001 From: whikernel Date: Thu, 14 Nov 2024 10:22:01 +0100 Subject: [PATCH 23/26] [FIX] Bump issue --- source/app/configuration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/app/configuration.py b/source/app/configuration.py index a37975327..e4db364f0 100644 --- a/source/app/configuration.py +++ b/source/app/configuration.py @@ -263,7 +263,7 @@ class CeleryConfig: # --------- APP --------- class Config: # Handled by bumpversion - IRIS_VERSION = "vIRIS_VERSION = "v2.4.15"" # DO NOT EDIT THIS LINE MANUALLY + IRIS_VERSION = "v2.4.15" # DO NOT EDIT THIS LINE MANUALLY if os.environ.get('IRIS_DEMO_VERSION') is not None and os.environ.get('IRIS_DEMO_VERSION') != 'None': IRIS_VERSION = os.environ.get('IRIS_DEMO_VERSION') From 9f3a725e11f0a226d20e2c84e251fe866736bb25 Mon Sep 17 00:00:00 2001 From: whikernel Date: Thu, 14 Nov 2024 10:22:13 +0100 Subject: [PATCH 24/26] =?UTF-8?q?Bump=20version:=202.4.15=20=E2=86=92=202.?= =?UTF-8?q?4.16?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- README.md | 4 ++-- docker-compose.yml | 8 ++++---- source/app/configuration.py | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 46c6b672c..2dbd64e99 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2.4.15 +current_version = 2.4.16 commit = True tag = True parse = (?P\d+)\.(?P\d+)\.(?P\d+)(-(?P.*)-(?P\d+))? diff --git a/README.md b/README.md index 98ce3992a..3c0667f7e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

Incident Response Investigation System
- Current Version vv2.4.15 + Current Version vv2.4.16
Online Demonstration

@@ -52,7 +52,7 @@ git clone https://github.com/dfir-iris/iris-web.git cd iris-web # Checkout to the last tagged version -git checkout vv2.4.15 +git checkout vv2.4.16 # Copy the environment file cp .env.model .env diff --git a/docker-compose.yml b/docker-compose.yml index 4cc7ac21a..956075e44 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,25 +25,25 @@ services: extends: file: docker-compose.base.yml service: db - image: ${DB_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_db}:${DB_IMAGE_TAG:-v-v2.4.15} + image: ${DB_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_db}:${DB_IMAGE_TAG:-v-v2.4.16} app: extends: file: docker-compose.base.yml service: app - image: ${APP_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_app}:${APP_IMAGE_TAG:-v-v2.4.15} + image: ${APP_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_app}:${APP_IMAGE_TAG:-v-v2.4.16} worker: extends: file: docker-compose.base.yml service: worker - image: ${APP_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_app}:${APP_IMAGE_TAG:-v-v2.4.15} + image: ${APP_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_app}:${APP_IMAGE_TAG:-v-v2.4.16} nginx: extends: file: docker-compose.base.yml service: nginx - image: ${NGINX_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_nginx}:${NGINX_IMAGE_TAG:-v-v2.4.15} + image: ${NGINX_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_nginx}:${NGINX_IMAGE_TAG:-v-v2.4.16} volumes: iris-downloads: diff --git a/source/app/configuration.py b/source/app/configuration.py index e4db364f0..3585052fd 100644 --- a/source/app/configuration.py +++ b/source/app/configuration.py @@ -263,7 +263,7 @@ class CeleryConfig: # --------- APP --------- class Config: # Handled by bumpversion - IRIS_VERSION = "v2.4.15" # DO NOT EDIT THIS LINE MANUALLY + IRIS_VERSION = "v2.4.16" # DO NOT EDIT THIS LINE MANUALLY if os.environ.get('IRIS_DEMO_VERSION') is not None and os.environ.get('IRIS_DEMO_VERSION') != 'None': IRIS_VERSION = os.environ.get('IRIS_DEMO_VERSION') From de67335e0a7b75e3cf1de154efbb50a4c5b85600 Mon Sep 17 00:00:00 2001 From: whikernel Date: Thu, 14 Nov 2024 11:08:32 +0100 Subject: [PATCH 25/26] [FIX] Docker compose issue due to bump --- docker-compose.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 956075e44..b9b24a3f1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,25 +25,25 @@ services: extends: file: docker-compose.base.yml service: db - image: ${DB_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_db}:${DB_IMAGE_TAG:-v-v2.4.16} + image: ${DB_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_db}:${DB_IMAGE_TAG:-v2.4.16} app: extends: file: docker-compose.base.yml service: app - image: ${APP_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_app}:${APP_IMAGE_TAG:-v-v2.4.16} + image: ${APP_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_app}:${APP_IMAGE_TAG:-v2.4.16} worker: extends: file: docker-compose.base.yml service: worker - image: ${APP_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_app}:${APP_IMAGE_TAG:-v-v2.4.16} + image: ${APP_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_app}:${APP_IMAGE_TAG:-v2.4.16} nginx: extends: file: docker-compose.base.yml service: nginx - image: ${NGINX_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_nginx}:${NGINX_IMAGE_TAG:-v-v2.4.16} + image: ${NGINX_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_nginx}:${NGINX_IMAGE_TAG:-v2.4.16} volumes: iris-downloads: From 37f2eebf7265fb1a69791331d47900d270c73e89 Mon Sep 17 00:00:00 2001 From: whikernel Date: Thu, 14 Nov 2024 11:09:39 +0100 Subject: [PATCH 26/26] [FIX] Fixed readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3c0667f7e..04514642c 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

Incident Response Investigation System
- Current Version vv2.4.16 + Current Version v2.4.16
Online Demonstration

@@ -52,7 +52,7 @@ git clone https://github.com/dfir-iris/iris-web.git cd iris-web # Checkout to the last tagged version -git checkout vv2.4.16 +git checkout v2.4.16 # Copy the environment file cp .env.model .env