refactor: store files on fs not in db

This commit is contained in:
Dobin Rutishauser
2025-11-07 12:19:33 +01:00
parent 698540796d
commit 39f59aceb4
8 changed files with 54 additions and 15 deletions
+1
View File
@@ -8,3 +8,4 @@ config.env
.vscode/ .vscode/
*.yaml *.yaml
todo.md todo.md
upload/
+9 -2
View File
@@ -4,9 +4,11 @@ from typing import Optional, Dict
import time import time
import json import json
from datetime import datetime from datetime import datetime
from detonatorapi.edr_parser.edr_parser import EdrParser
from typing import List from typing import List
import os
from detonatorapi.edr_parser.edr_parser import EdrParser
from detonatorapi.settings import UPLOAD_DIR
from detonatorapi.database import Scan, get_db_for_thread from detonatorapi.database import Scan, get_db_for_thread
from detonatorapi.db_interface import db_scan_change_status_quick, db_scan_add_log from detonatorapi.db_interface import db_scan_change_status_quick, db_scan_add_log
from detonatorapi.agent.agent_api import AgentApi from detonatorapi.agent.agent_api import AgentApi
@@ -82,7 +84,12 @@ def scan_file_with_agent(scan_id: int) -> bool:
filename = db_scan.file.filename filename = db_scan.file.filename
exec_arguments = db_scan.file.exec_arguments exec_arguments = db_scan.file.exec_arguments
file_content = db_scan.file.content
# Read file content from disk
file_path = os.path.join(UPLOAD_DIR, db_scan.file.filename)
with open(file_path, 'rb') as f:
file_content = f.read()
runtime = db_scan.runtime runtime = db_scan.runtime
drop_path = db_scan.drop_path drop_path = db_scan.drop_path
rededr_port = db_scan.profile.rededr_port rededr_port = db_scan.profile.rededr_port
-1
View File
@@ -38,7 +38,6 @@ class File(Base):
__tablename__ = "files" __tablename__ = "files"
id: Mapped[int] = Column(Integer, primary_key=True, index=True) id: Mapped[int] = Column(Integer, primary_key=True, index=True)
content: Mapped[bytes] = Column(LargeBinary, nullable=False)
filename: Mapped[str] = Column(String(255), nullable=False) filename: Mapped[str] = Column(String(255), nullable=False)
exec_arguments: Mapped[str] = Column(String(255), nullable=True) exec_arguments: Mapped[str] = Column(String(255), nullable=True)
file_hash: Mapped[str] = Column(String(64), nullable=False, index=True) file_hash: Mapped[str] = Column(String(64), nullable=False, index=True)
+18 -4
View File
@@ -1,6 +1,11 @@
from typing import Optional, List from typing import Optional, List
from datetime import datetime from datetime import datetime
import logging import logging
import os
from .settings import UPLOAD_DIR
import random
import string
from werkzeug.utils import secure_filename
from .database import Scan, File, Profile, get_db_for_thread from .database import Scan, File, Profile, get_db_for_thread
from .utils import mylog from .utils import mylog
@@ -48,11 +53,20 @@ def db_scan_add_log(db, db_scan, log_message: str):
def db_create_file(db, filename: str, content: bytes, source_url: str = "", comment: str = "", exec_arguments: str = "", user: str = "") -> int: def db_create_file(db, filename: str, content: bytes, source_url: str = "", comment: str = "", exec_arguments: str = "", user: str = "") -> int:
file_hash = File.calculate_hash(content) file_hash = File.calculate_hash(content)
# prepend 4 random chars to filename to avoid collisions
filename = secure_filename(filename)
rand_str = ''.join(random.choices(string.ascii_letters + string.digits, k=4))
actual_filename = f"{rand_str}_{filename}"
# DB: Create file record # Write file content to disk
file_path = os.path.join(UPLOAD_DIR, f"{actual_filename}")
with open(file_path, 'wb') as f:
f.write(content)
# DB: Create file record with path instead of content
db_file = File( db_file = File(
content=content, filename=actual_filename,
filename=filename,
file_hash=file_hash, file_hash=file_hash,
source_url=source_url, source_url=source_url,
comment=comment, comment=comment,
@@ -62,7 +76,7 @@ def db_create_file(db, filename: str, content: bytes, source_url: str = "", comm
db.add(db_file) db.add(db_file)
db.commit() db.commit()
logger.info(f"DB: Created file {db_file.id} with filename: {filename}") logger.info(f"DB: Created file {db_file.id} with filename: {actual_filename}")
return db_file.id return db_file.id
+2 -7
View File
@@ -1,9 +1,7 @@
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
import logging import logging
import os
import random
import string
from dotenv import load_dotenv from dotenv import load_dotenv
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File as FastAPIFile, Form from fastapi import APIRouter, Depends, HTTPException, UploadFile, File as FastAPIFile, Form
from sqlalchemy.orm import Session, joinedload from sqlalchemy.orm import Session, joinedload
@@ -134,12 +132,9 @@ async def upload_file_and_scan(
filename = file.filename filename = file.filename
if not filename: if not filename:
raise HTTPException(status_code=400, detail="Filename cannot be empty") raise HTTPException(status_code=400, detail="Filename cannot be empty")
rand_str = ''.join(random.choices(string.ascii_letters + string.digits, k=4))
actual_filename = f"{rand_str}_{filename}"
logger.info(f"Uploading file: {actual_filename}")
file_content = await file.read() file_content = await file.read()
file_id = db_create_file(db, file_id = db_create_file(db,
filename=actual_filename, filename=filename,
content=file_content, content=file_content,
source_url=source_url or "", source_url=source_url or "",
comment=file_comment or "", comment=file_comment or "",
+3
View File
@@ -1,6 +1,9 @@
import os import os
from dotenv import load_dotenv from dotenv import load_dotenv
UPLOAD_DIR = "upload/"
# Load environment variables # Load environment variables
load_dotenv() load_dotenv()
+21 -1
View File
@@ -3,11 +3,13 @@ from fastapi.responses import Response
from sqlalchemy.orm import Session, joinedload from sqlalchemy.orm import Session, joinedload
from typing import List, Optional from typing import List, Optional
import logging import logging
import os
from .database import get_db, File, Scan from .database import get_db, File, Scan
from .schemas import FileResponse, FileWithScans from .schemas import FileResponse, FileWithScans
from .db_interface import db_create_file, db_create_scan, db_get_profile_by_name from .db_interface import db_create_file, db_create_scan, db_get_profile_by_name
from .token_auth import require_auth, get_user_from_request from .token_auth import require_auth, get_user_from_request
from .settings import UPLOAD_DIR
router = APIRouter() router = APIRouter()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -75,6 +77,15 @@ async def delete_file(
if db_file is None: if db_file is None:
raise HTTPException(status_code=404, detail="File not found") raise HTTPException(status_code=404, detail="File not found")
# Delete file from filesystem
file_path = os.path.join(UPLOAD_DIR, db_file.filename)
if os.path.exists(file_path):
try:
os.remove(file_path)
logger.info(f"Deleted file from disk: {file_path}")
except Exception as e:
logger.error(f"Failed to delete file from disk: {file_path}, error: {e}")
# Delete associated scans first # Delete associated scans first
db.query(Scan).filter(Scan.file_id == file_id).delete() db.query(Scan).filter(Scan.file_id == file_id).delete()
db.delete(db_file) db.delete(db_file)
@@ -93,8 +104,17 @@ async def download_file(
if db_file is None: if db_file is None:
raise HTTPException(status_code=404, detail="File not found") raise HTTPException(status_code=404, detail="File not found")
# Check if file exists on disk
file_path = os.path.join(UPLOAD_DIR, db_file.filename)
if not os.path.exists(file_path):
raise HTTPException(status_code=404, detail="File not found on disk")
# Read file content from disk
with open(file_path, 'rb') as f:
content = f.read()
return Response( return Response(
content=db_file.content, content=content,
media_type="application/octet-stream", media_type="application/octet-stream",
headers={ headers={
"Content-Disposition": f"attachment; filename={db_file.filename}" "Content-Disposition": f"attachment; filename={db_file.filename}"
View File