Skip to content

Sqlalchemy 2 + parent<>child relationship reworked #2646

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 57 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
e2bd101
Ruff (#2410)
doomedraven Feb 2, 2025
ff09024
Update zip_utils.py
doomedraven Feb 2, 2025
7871a71
Update zip_utils.py
doomedraven Feb 2, 2025
462fb0c
Merge branch 'master' into staging
doomedraven Feb 6, 2025
8db0425
Merge branch 'master' into staging
doomedraven Feb 7, 2025
82be043
Update rooter.py
doomedraven Feb 7, 2025
2299dc0
Merge branch 'master' into staging
doomedraven Feb 7, 2025
8228c21
Update plugins.py
doomedraven Feb 7, 2025
6f4df2b
Update abstracts.py
doomedraven Feb 7, 2025
e812fe1
Update plugins.py
doomedraven Feb 7, 2025
bf15a9d
Update plugins.py
doomedraven Feb 7, 2025
07b9f84
Merge branch 'master' into staging
doomedraven Feb 7, 2025
54de070
Merge branch 'master' into staging
doomedraven Feb 7, 2025
b392b01
Merge branch 'master' into staging
doomedraven Feb 9, 2025
bcec6d9
Update tls.py
doomedraven Feb 9, 2025
269a449
sync
doomedraven Feb 11, 2025
e145063
Merge branch 'master' into staging
doomedraven Feb 12, 2025
a8f768a
sync
doomedraven Feb 12, 2025
3cd15fd
Merge branch 'master' into staging
doomedraven Feb 15, 2025
50d7f54
Downloaders (#2493)
doomedraven Feb 16, 2025
12999c2
ci: Update requirements.txt
actions-user Feb 16, 2025
9145bac
improve docs
doomedraven Feb 17, 2025
07afabf
typo
doomedraven Feb 17, 2025
61bbeae
fix typo
doomedraven Feb 17, 2025
47b0c34
Merge branch 'master' into staging
doomedraven Feb 18, 2025
37e8eb8
Update load_extra_modules.py
doomedraven Feb 18, 2025
c12b2f0
sync
doomedraven Feb 18, 2025
85777cc
fix static
doomedraven Feb 19, 2025
d8082fd
fix rtf
doomedraven Feb 19, 2025
ae22386
Merge branch 'master' into staging
doomedraven Feb 28, 2025
0b1c8e3
sync
doomedraven Mar 1, 2025
5c37b79
Create test_strings.py
doomedraven Mar 1, 2025
d51af8f
Update database.py
doomedraven Mar 1, 2025
d4e6557
Update database.py
doomedraven Mar 1, 2025
5e45d94
Update test_strings.py
doomedraven Mar 1, 2025
f2d604a
Update changelog.md
doomedraven Mar 1, 2025
747a069
Merge branch 'master' into staging
doomedraven Mar 1, 2025
245bed8
Merge branch 'master' into staging
doomedraven Jun 6, 2025
7c3e65b
ci: Update requirements.txt
actions-user Jun 6, 2025
7a27af3
Merge branch 'master' into staging
doomedraven Jun 12, 2025
3c287cd
ci: Update requirements.txt
actions-user Jun 12, 2025
4f6e17c
Merge branch 'master' into staging
doomedraven Jul 3, 2025
8555305
sqlalchemy 2 (#2636)
doomedraven Jul 8, 2025
be0e80a
ci: Update requirements.txt
actions-user Jul 8, 2025
a637ad1
Update dist.py
doomedraven Jul 10, 2025
746be3f
Merge branch 'master' into staging
doomedraven Jul 10, 2025
28e4ddf
db cleanup (#2644)
doomedraven Jul 14, 2025
13e1e1e
Proper relationship parent-child samples (#2645)
doomedraven Jul 15, 2025
100b7cf
drop also old dist migrations
doomedraven Jul 15, 2025
32e3170
Update web_utils.py
doomedraven Jul 15, 2025
cc2584a
Update lib/cuckoo/common/dist_db.py
doomedraven Jul 15, 2025
150b933
Update 2. Database cleanup.py
doomedraven Jul 15, 2025
d4380a3
Update web_utils.py
doomedraven Jul 16, 2025
e44ea81
Update dist.py
doomedraven Jul 16, 2025
33a2a8c
Create iocs.py
doomedraven Jul 16, 2025
b0e83f4
Update views.py
doomedraven Jul 17, 2025
c325da5
Revert "Update views.py"
doomedraven Jul 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions conf/default/web.conf.default
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,6 @@ enabled = no
[display_office_martians]
enabled = no

[display_shrike]
enabled = no

[display_task_tags]
# displays custom tags, if set during sample submission
enabled = no
Expand Down
175 changes: 96 additions & 79 deletions lib/cuckoo/common/dist_db.py
Original file line number Diff line number Diff line change
@@ -1,112 +1,126 @@
import sys
from datetime import datetime
from typing import List, Optional

# http://pythoncentral.io/introductory-tutorial-python-sqlalchemy/
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Index, Integer, String, Table, Text, create_engine
from sqlalchemy import (
Column,
create_engine,
DateTime,
ForeignKey,
Integer,
String,
Table,
Text,
)
from sqlalchemy.exc import OperationalError
from sqlalchemy.orm import declarative_base, relationship, sessionmaker
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, sessionmaker
from sqlalchemy.types import TypeDecorator

Base = declarative_base()

# 1. Use DeclarativeBase as the modern starting point
class Base(DeclarativeBase):
pass


schema = "83fd58842164"

# This association table definition is correct and doesn't need changes
worker_exitnodes = Table(
"worker_exitnodes",
Base.metadata,
Column("node_id", Integer, ForeignKey("node.id"), primary_key=True),
Column("exit_id", Integer, ForeignKey("exitnodes.id"), primary_key=True),
)

class ExitNodes(Base):
"""Exit nodes to route traffic."""

# 2. Modernized all models with Mapped/mapped_column and explicit relationships
class ExitNodes(Base):
__tablename__ = "exitnodes"

id = Column(Integer(), primary_key=True)
name = Column(String(255), nullable=False, unique=True)
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(255), unique=True)

def __repr__(self):
return f"<Exit node('{self.id}','{self.name}')>"
# This relationship completes the link from the Node model
nodes: Mapped[List["Node"]] = relationship(secondary=worker_exitnodes, back_populates="exitnodes")

def __init__(self, name):
self.name = name
def __repr__(self) -> str:
return f"<ExitNode(id={self.id}, name='{self.name}')>"


# Secondary table used in association Worker - Exit node.
worker_exitnodes = Table(
"worker_exitnodes",
Base.metadata,
Column("node_id", Integer, ForeignKey("node.id")),
Column("exit_id", Integer, ForeignKey("exitnodes.id")),
)
class Node(Base):
__tablename__ = "node"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(Text)
url: Mapped[Optional[str]] = mapped_column(Text)
enabled: Mapped[bool] = mapped_column(default=False)
apikey: Mapped[str] = mapped_column(String(255))
last_check: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=False))

# Replaced legacy `backref` with explicit `back_populates`
machines: Mapped[List["Machine"]] = relationship(back_populates="node")
exitnodes: Mapped[List["ExitNodes"]] = relationship(
secondary=worker_exitnodes, back_populates="nodes", lazy="subquery"
) # really need lazy?


# The TypeDecorator is a valid pattern; added type hints for clarity
class StringList(TypeDecorator):
"""List of comma-separated strings as field."""
"""Saves a Python list of strings as a single comma-separated string in the DB."""

impl = Text
cache_ok = True # Indicates the type is safe to cache

def process_bind_param(self, value, dialect):
def process_bind_param(self, value: Optional[List[str]], dialect) -> Optional[str]:
if value is None:
return None
return ", ".join(value)

def process_result_value(self, value, dialect):
return value.split(", ")
def process_result_value(self, value: Optional[str], dialect) -> Optional[List[str]]:
if value is None:
return None
return [item.strip() for item in value.split(",")]


class Machine(Base):
"""Machine database model related to a Cuckoo node."""

__tablename__ = "machine"
id = Column(Integer, primary_key=True)
name = Column(Text, nullable=False)
platform = Column(Text, nullable=False)
tags = Column(StringList)
node_id = Column(Integer, ForeignKey("node.id"))
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(Text)
platform: Mapped[str] = mapped_column(Text)
tags: Mapped[Optional[List[str]]] = mapped_column(StringList)
node_id: Mapped[Optional[int]] = mapped_column(ForeignKey("node.id"))


class Node(Base):
"""Cuckoo node database model."""

__tablename__ = "node"
id = Column(Integer, primary_key=True)
name = Column(Text, nullable=False)
url = Column(Text, nullable=True)
enabled = Column(Boolean, default=False)
apikey = Column(String(255), nullable=False)
last_check = Column(DateTime(timezone=False))
machines = relationship(Machine, backref="node", lazy="dynamic")
exitnodes = relationship(ExitNodes, secondary=worker_exitnodes, backref="node", lazy="subquery")
# This relationship completes the link from the Node model
node: Mapped["Node"] = relationship(back_populates="machines")


class Task(Base):
"""Analysis task database model."""

__tablename__ = "task"
id = Column(Integer, primary_key=True)
path = Column(Text)
category = Column(Text)
package = Column(Text)
timeout = Column(Integer)
priority = Column(Integer)
options = Column(Text)
machine = Column(Text)
platform = Column(Text)
route = Column(Text)
tags = Column(Text)
custom = Column(Text)
memory = Column(Text)
clock = Column(DateTime(timezone=False), default=datetime.now(), nullable=False)
enforce_timeout = Column(Text)
tlp = Column(Text, nullable=True)
# Cuckoo node and Task ID this has been submitted to.
node_id = Column(Integer, ForeignKey("node.id"))
task_id = Column(Integer)
finished = Column(Boolean, nullable=False, default=False)
main_task_id = Column(Integer)
retrieved = Column(Boolean, nullable=False, default=False)
notificated = Column(Boolean, nullable=True, default=False)
deleted = Column(Boolean, nullable=False, default=False)

__table_args__ = (
Index("node_id_index", "node_id"),
Index("task_id_index", "task_id"),
Index("main_task_id_index", "main_task_id", unique=False),
)
id: Mapped[int] = mapped_column(primary_key=True)
path: Mapped[Optional[str]] = mapped_column(Text)
category: Mapped[Optional[str]] = mapped_column(Text)
package: Mapped[Optional[str]] = mapped_column(Text)
timeout: Mapped[Optional[int]] = mapped_column(Integer)
priority: Mapped[Optional[int]] = mapped_column(Integer)
options: Mapped[Optional[str]] = mapped_column(Text)
machine: Mapped[Optional[str]] = mapped_column(Text)
platform: Mapped[Optional[str]] = mapped_column(Text)
route: Mapped[Optional[str]] = mapped_column(Text)
tags: Mapped[Optional[str]] = mapped_column(Text)
custom: Mapped[Optional[str]] = mapped_column(Text)
memory: Mapped[Optional[str]] = mapped_column(Text)
clock: Mapped[datetime] = mapped_column(default=datetime.now)
enforce_timeout: Mapped[Optional[str]] = mapped_column(Text)
tlp: Mapped[Optional[str]] = mapped_column(Text)

node_id: Mapped[Optional[int]] = mapped_column(ForeignKey("node.id"), index=True)
task_id: Mapped[Optional[int]] = mapped_column(index=True)
main_task_id: Mapped[Optional[int]] = mapped_column(index=True)

finished: Mapped[bool] = mapped_column(default=False)
retrieved: Mapped[bool] = mapped_column(default=False)
notificated: Mapped[bool] = mapped_column(default=False)
deleted: Mapped[bool] = mapped_column(default=False)

def __init__(
self,
Expand Down Expand Up @@ -150,11 +164,14 @@ def __init__(
self.tlp = tlp


def create_session(db_connectionn: str, echo=False) -> sessionmaker:
# ToDo add schema version check
# 4. Modernized database initialization function
def create_session(db_connection: str, echo: bool = False) -> sessionmaker:
"""Initializes the database engine and creates tables."""
try:
engine = create_engine(db_connectionn, echo=echo) # pool_size=40, max_overflow=0,
engine = create_engine(db_connection, echo=echo)
Base.metadata.create_all(engine)
return sessionmaker(autoflush=True, bind=engine)
# Return the session factory for use in the application
return sessionmaker(bind=engine, autoflush=False)
except OperationalError as e:
sys.exit(e)
print(f"Database Error: {e}")
sys.exit(1)
Loading
Loading