Skip to content

Commit

Permalink
Add iocs to alerts (#328)
Browse files Browse the repository at this point in the history
* `refactor: Add IoC and AlertToIoC models`

* `refactor: Add IoC and AlertToIoC models`

* `refactor: delete ioc from alert`

* `refactor: Add IoC collection to list alerts`

* `refactor: Add IoC collection to list alerts`

* `feat: Add IoC filtering to list alerts`

* chore: update dependencies in frontend

* feat: add alert iocs list and creation form

* feat: add CollapseKeepAlive component

* refactor: incident list

* feat: improved ioc form validation

* feat: add ioc filter on alerts list

* `refactor: Update connector IDs for Huntress integration`

* precommit fixes

---------

Co-authored-by: Davide Di Modica <[email protected]>
  • Loading branch information
taylorwalton and Linko91 authored Nov 4, 2024
1 parent 956dd25 commit 73ac945
Show file tree
Hide file tree
Showing 23 changed files with 1,489 additions and 748 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"hoverable",
"iconoir",
"Indicies",
"iocs",
"lastupdate",
"linebreak",
"Logsource",
Expand Down
2 changes: 2 additions & 0 deletions backend/alembic/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from app.incidents.models import AlertContext
from app.incidents.models import AlertTag
from app.incidents.models import AlertTitleFieldName
from app.incidents.models import AlertToIoC
from app.incidents.models import AlertToTag
from app.incidents.models import Asset
from app.incidents.models import AssetFieldName
Expand All @@ -36,6 +37,7 @@
from app.incidents.models import Comment
from app.incidents.models import CustomerCodeFieldName
from app.incidents.models import FieldName
from app.incidents.models import IoC
from app.incidents.models import Notification
from app.integrations.alert_creation_settings.models.alert_creation_settings import (
AlertCreationSettings,
Expand Down
53 changes: 53 additions & 0 deletions backend/alembic/versions/33e4754d845b_add_ioc_table_for_alerts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Add ioc table for alerts
Revision ID: 33e4754d845b
Revises: a1e4beb87498
Create Date: 2024-10-30 15:16:50.509086
"""
from typing import Sequence
from typing import Union

import sqlalchemy as sa

from alembic import op

# revision identifiers, used by Alembic.
revision: str = "33e4754d845b"
down_revision: Union[str, None] = "a1e4beb87498"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"incident_management_ioc",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("value", sa.String(length=250), nullable=False),
sa.Column("type", sa.String(length=50), nullable=False),
sa.Column("description", sa.String(length=10000), nullable=True),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"incident_management_alert_to_ioc",
sa.Column("alert_id", sa.Integer(), nullable=False),
sa.Column("ioc_id", sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(
["alert_id"],
["incident_management_alert.id"],
),
sa.ForeignKeyConstraint(
["ioc_id"],
["incident_management_ioc.id"],
),
sa.PrimaryKeyConstraint("alert_id", "ioc_id"),
)
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("incident_management_alert_to_ioc")
op.drop_table("incident_management_ioc")
# ### end Alembic commands ###
20 changes: 20 additions & 0 deletions backend/app/incidents/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,25 @@
from sqlmodel import Text


class IoC(SQLModel, table=True):
__tablename__ = "incident_management_ioc"
id: Optional[int] = Field(default=None, primary_key=True)
value: str = Field(nullable=False)
type: str = Field(max_length=50, nullable=False) # e.g., IP address, domain, URL, etc.
description: Optional[str] = Field(sa_column=Text, nullable=True)

alerts: List["AlertToIoC"] = Relationship(back_populates="ioc")


class AlertToIoC(SQLModel, table=True):
__tablename__ = "incident_management_alert_to_ioc"
alert_id: int = Field(foreign_key="incident_management_alert.id", primary_key=True)
ioc_id: int = Field(foreign_key="incident_management_ioc.id", primary_key=True)

alert: "Alert" = Relationship(back_populates="iocs")
ioc: "IoC" = Relationship(back_populates="alerts")


class Alert(SQLModel, table=True):
__tablename__ = "incident_management_alert"
id: Optional[int] = Field(default=None, primary_key=True)
Expand All @@ -28,6 +47,7 @@ class Alert(SQLModel, table=True):
assets: List["Asset"] = Relationship(back_populates="alert")
cases: List["CaseAlertLink"] = Relationship(back_populates="alert")
tags: List["AlertToTag"] = Relationship(back_populates="alert")
iocs: List["AlertToIoC"] = Relationship(back_populates="alert")


class AlertTag(SQLModel, table=True):
Expand Down
49 changes: 49 additions & 0 deletions backend/app/incidents/routes/db_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
from app.incidents.schema.db_operations import AlertContextCreate
from app.incidents.schema.db_operations import AlertContextResponse
from app.incidents.schema.db_operations import AlertCreate
from app.incidents.schema.db_operations import AlertIoCCreate
from app.incidents.schema.db_operations import AlertIoCDelete
from app.incidents.schema.db_operations import AlertIoCResponse
from app.incidents.schema.db_operations import AlertOutResponse
from app.incidents.schema.db_operations import AlertResponse
from app.incidents.schema.db_operations import AlertStatus
Expand Down Expand Up @@ -82,6 +85,7 @@
from app.incidents.services.db_operations import alerts_closed_by_asset_name
from app.incidents.services.db_operations import alerts_closed_by_assigned_to
from app.incidents.services.db_operations import alerts_closed_by_customer_code
from app.incidents.services.db_operations import alerts_closed_by_ioc
from app.incidents.services.db_operations import alerts_closed_by_source
from app.incidents.services.db_operations import alerts_closed_by_tag
from app.incidents.services.db_operations import alerts_closed_multiple_filters
Expand All @@ -90,6 +94,7 @@
from app.incidents.services.db_operations import alerts_in_progress_by_assest_name
from app.incidents.services.db_operations import alerts_in_progress_by_assigned_to
from app.incidents.services.db_operations import alerts_in_progress_by_customer_code
from app.incidents.services.db_operations import alerts_in_progress_by_ioc
from app.incidents.services.db_operations import alerts_in_progress_by_source
from app.incidents.services.db_operations import alerts_in_progress_by_tag
from app.incidents.services.db_operations import alerts_in_progress_multiple_filters
Expand All @@ -98,23 +103,27 @@
from app.incidents.services.db_operations import alerts_open_by_assest_name
from app.incidents.services.db_operations import alerts_open_by_assigned_to
from app.incidents.services.db_operations import alerts_open_by_customer_code
from app.incidents.services.db_operations import alerts_open_by_ioc
from app.incidents.services.db_operations import alerts_open_by_source
from app.incidents.services.db_operations import alerts_open_by_tag
from app.incidents.services.db_operations import alerts_open_multiple_filters
from app.incidents.services.db_operations import alerts_total_by_assigned_to
from app.incidents.services.db_operations import alerts_total_by_customer_code
from app.incidents.services.db_operations import alerts_total_by_ioc
from app.incidents.services.db_operations import alerts_total_by_source
from app.incidents.services.db_operations import alerts_total_by_tag
from app.incidents.services.db_operations import alerts_total_multiple_filters
from app.incidents.services.db_operations import create_alert
from app.incidents.services.db_operations import create_alert_context
from app.incidents.services.db_operations import create_alert_ioc
from app.incidents.services.db_operations import create_alert_tag
from app.incidents.services.db_operations import create_asset
from app.incidents.services.db_operations import create_case
from app.incidents.services.db_operations import create_case_alert_link
from app.incidents.services.db_operations import create_case_from_alert
from app.incidents.services.db_operations import create_comment
from app.incidents.services.db_operations import delete_alert
from app.incidents.services.db_operations import delete_alert_ioc
from app.incidents.services.db_operations import delete_alert_tag
from app.incidents.services.db_operations import delete_alert_title_name
from app.incidents.services.db_operations import delete_asset_name
Expand All @@ -140,6 +149,7 @@
from app.incidents.services.db_operations import list_alerts
from app.incidents.services.db_operations import list_alerts_by_asset_name
from app.incidents.services.db_operations import list_alerts_by_customer_code
from app.incidents.services.db_operations import list_alerts_by_ioc
from app.incidents.services.db_operations import list_alerts_by_source
from app.incidents.services.db_operations import list_alerts_by_tag
from app.incidents.services.db_operations import list_alerts_by_title
Expand Down Expand Up @@ -407,6 +417,38 @@ async def create_asset_endpoint(asset: AssetCreate, db: AsyncSession = Depends(g
return AssetResponse(asset=await create_asset(asset, db), success=True, message="Asset created successfully")


@incidents_db_operations_router.post("/alert/ioc", response_model=AlertIoCResponse)
async def create_alert_ioc_endpoint(ioc: AlertIoCCreate, db: AsyncSession = Depends(get_db)):
return AlertIoCResponse(alert_ioc=await create_alert_ioc(ioc, db), success=True, message="Alert IoC created successfully")


@incidents_db_operations_router.get("/alert/ioc/{ioc_value}", response_model=AlertOutResponse)
async def list_alerts_by_ioc_value_endpoint(
ioc_value: str,
db: AsyncSession = Depends(get_db),
page: int = Query(1, ge=1),
page_size: int = Query(25, ge=1),
):
return AlertOutResponse(
alerts=await list_alerts_by_ioc(ioc_value, db, page, page_size),
total=await alerts_total_by_ioc(db, ioc_value),
open=await alerts_open_by_ioc(db, ioc_value),
in_progress=await alerts_in_progress_by_ioc(db, ioc_value),
closed=await alerts_closed_by_ioc(db, ioc_value),
success=True,
message="Alerts retrieved successfully",
)


@incidents_db_operations_router.delete("/alert/ioc", response_model=AlertIoCResponse)
async def delete_alert_ioc_endpoint(ioc: AlertIoCDelete, db: AsyncSession = Depends(get_db)):
return AlertIoCResponse(
alert_ioc=await delete_alert_ioc(ioc=ioc, db=db),
success=True,
message="Alert IoC deleted successfully",
)


@incidents_db_operations_router.post("/alert/tag", response_model=AlertTagResponse)
async def create_alert_tag_endpoint(alert_tag: AlertTagCreate, db: AsyncSession = Depends(get_db)):
return AlertTagResponse(alert_tag=await create_alert_tag(alert_tag, db), success=True, message="Alert tag created successfully")
Expand Down Expand Up @@ -620,6 +662,7 @@ async def list_alerts_multiple_filters_endpoint(
asset_name: Optional[str] = Query(None),
status: Optional[str] = Query(None),
tags: Optional[List[str]] = Query(None),
ioc_value: Optional[str] = Query(None),
page: int = Query(1, ge=1),
page_size: int = Query(25, ge=1),
order: str = Query("desc", regex="^(asc|desc)$"),
Expand All @@ -636,6 +679,7 @@ async def list_alerts_multiple_filters_endpoint(
- asset_name (str, optional): Filter by asset name.
- status (str, optional): Filter by status.
- tags (List[str], optional): Filter by tags.
- ioc_value (str, optional): Filter by IoC value.
- page (int, default=1): Page number.
- page_size (int, default=25): Number of alerts per page.
- order (str, default='desc'): Sorting order ('asc' or 'desc').
Expand All @@ -659,6 +703,7 @@ async def list_alerts_multiple_filters_endpoint(
asset_name=asset_name,
status=status,
tags=tags,
ioc_value=ioc_value,
db=db,
page=page,
page_size=page_size,
Expand All @@ -672,6 +717,7 @@ async def list_alerts_multiple_filters_endpoint(
asset_name=asset_name,
status=status,
tags=tags,
ioc_value=ioc_value,
db=db,
),
open=await alerts_open_multiple_filters(
Expand All @@ -682,6 +728,7 @@ async def list_alerts_multiple_filters_endpoint(
asset_name=asset_name,
status=status,
tags=tags,
ioc_value=ioc_value,
db=db,
),
in_progress=await alerts_in_progress_multiple_filters(
Expand All @@ -692,6 +739,7 @@ async def list_alerts_multiple_filters_endpoint(
asset_name=asset_name,
status=status,
tags=tags,
ioc_value=ioc_value,
db=db,
),
closed=await alerts_closed_multiple_filters(
Expand All @@ -702,6 +750,7 @@ async def list_alerts_multiple_filters_endpoint(
asset_name=asset_name,
status=status,
tags=tags,
ioc_value=ioc_value,
db=db,
),
success=True,
Expand Down
44 changes: 44 additions & 0 deletions backend/app/incidents/schema/db_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
from typing import List
from typing import Optional

from fastapi import HTTPException
from pydantic import BaseModel
from pydantic import validator

from app.incidents.models import Alert
from app.incidents.models import AlertContext
from app.incidents.models import AlertTag
from app.incidents.models import AlertToIoC
from app.incidents.models import Asset
from app.incidents.models import Case
from app.incidents.models import CaseAlertLink
Expand Down Expand Up @@ -136,6 +138,35 @@ class AlertTagResponse(BaseModel):
message: str


class AlertIocValue(str, Enum):
IP = "IP"
DOMAIN = "DOMAIN"
HASH = "HASH"
URL = "URL"


class AlertIoCCreate(BaseModel):
alert_id: int
ioc_value: str
ioc_type: AlertIocValue
ioc_description: Optional[str] = None

@validator("ioc_type")
def validate_ioc_type(cls, v):
if v not in AlertIocValue:
raise HTTPException(
status_code=400,
detail=f"Invalid IoC type. Must be one of {', '.join([ioc.value for ioc in AlertIocValue])}",
)
return v


class AlertIoCResponse(BaseModel):
alert_ioc: AlertToIoC
success: bool
message: str


class CaseResponse(BaseModel):
case: Case
success: bool
Expand Down Expand Up @@ -258,6 +289,11 @@ class AlertTagDelete(BaseModel):
tag_id: int


class AlertIoCDelete(BaseModel):
alert_id: int
ioc_id: int


class CommentBase(BaseModel):
user_name: str
alert_id: int
Expand All @@ -278,6 +314,13 @@ class AssetBase(BaseModel):
index_name: str


class IoCBase(BaseModel):
value: str
type: AlertIocValue
description: Optional[str] = None
id: int


class AlertOut(BaseModel):
id: int
alert_creation_time: datetime
Expand All @@ -292,6 +335,7 @@ class AlertOut(BaseModel):
assets: List[AssetBase] = []
tags: List[AlertTagBase] = []
linked_cases: List[LinkedCaseCreate] = []
iocs: List[IoCBase] = []


class AlertOutResponse(BaseModel):
Expand Down
Loading

0 comments on commit 73ac945

Please sign in to comment.