Enhance vulnerability scanning by linking vulnerabilities to scan jobs and updating scan_all_images method to accept scan_job_id
This commit is contained in:
3
main.py
3
main.py
@@ -122,7 +122,7 @@ def background_vulnerability_scan(job_id: int):
|
|||||||
websocket_manager.notify_scan_started_sync("vulnerability", job_id, "Vulnerability scan started")
|
websocket_manager.notify_scan_started_sync("vulnerability", job_id, "Vulnerability scan started")
|
||||||
|
|
||||||
vuln_scanner = VulnerabilityScanner()
|
vuln_scanner = VulnerabilityScanner()
|
||||||
vuln_scanner.scan_all_images(db)
|
vuln_scanner.scan_all_images(db, job_id)
|
||||||
|
|
||||||
if job:
|
if job:
|
||||||
job.status = "completed"
|
job.status = "completed"
|
||||||
@@ -185,6 +185,7 @@ class ImageResponse(BaseModel):
|
|||||||
class VulnerabilityResponse(BaseModel):
|
class VulnerabilityResponse(BaseModel):
|
||||||
id: int
|
id: int
|
||||||
image_id: int
|
image_id: int
|
||||||
|
scan_job_id: Optional[int] = None
|
||||||
vulnerability_id: str
|
vulnerability_id: str
|
||||||
severity: str
|
severity: str
|
||||||
title: Optional[str] = None
|
title: Optional[str] = None
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ class Vulnerability(Base):
|
|||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
image_id = Column(Integer, ForeignKey("images.id"), nullable=False)
|
image_id = Column(Integer, ForeignKey("images.id"), nullable=False)
|
||||||
|
scan_job_id = Column(Integer, ForeignKey("scan_jobs.id"), nullable=True)
|
||||||
vulnerability_id = Column(String(100), nullable=False)
|
vulnerability_id = Column(String(100), nullable=False)
|
||||||
severity = Column(String(20), nullable=False)
|
severity = Column(String(20), nullable=False)
|
||||||
title = Column(String(500), nullable=True)
|
title = Column(String(500), nullable=True)
|
||||||
@@ -132,6 +133,7 @@ class Vulnerability(Base):
|
|||||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
|
||||||
image = relationship("Image", back_populates="vulnerabilities")
|
image = relationship("Image", back_populates="vulnerabilities")
|
||||||
|
scan_job = relationship("ScanJob", back_populates="vulnerabilities")
|
||||||
|
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
UniqueConstraint(
|
UniqueConstraint(
|
||||||
@@ -170,6 +172,8 @@ class ScanJob(Base):
|
|||||||
created_at = Column(DateTime, default=datetime.utcnow)
|
created_at = Column(DateTime, default=datetime.utcnow)
|
||||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
|
||||||
|
vulnerabilities = relationship("Vulnerability", back_populates="scan_job", cascade="all, delete-orphan")
|
||||||
|
|
||||||
|
|
||||||
DATABASE_URL = "sqlite:///./gitlab_docker_tracker.db"
|
DATABASE_URL = "sqlite:///./gitlab_docker_tracker.db"
|
||||||
engine = create_engine(DATABASE_URL, echo=False)
|
engine = create_engine(DATABASE_URL, echo=False)
|
||||||
|
|||||||
@@ -23,18 +23,18 @@ class VulnerabilityScanner:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def scan_image_vulnerabilities(
|
def scan_image_vulnerabilities(
|
||||||
self, db: Session, image: Image
|
self, db: Session, image: Image, scan_job_id: Optional[int] = None
|
||||||
) -> List[Vulnerability]:
|
) -> List[Vulnerability]:
|
||||||
vulnerabilities = []
|
vulnerabilities = []
|
||||||
|
|
||||||
if self.trivy_available:
|
if self.trivy_available:
|
||||||
vulnerabilities.extend(self._scan_with_trivy(db, image))
|
vulnerabilities.extend(self._scan_with_trivy(db, image, scan_job_id))
|
||||||
else:
|
else:
|
||||||
vulnerabilities.extend(self._scan_with_api(db, image))
|
vulnerabilities.extend(self._scan_with_api(db, image, scan_job_id))
|
||||||
|
|
||||||
return vulnerabilities
|
return vulnerabilities
|
||||||
|
|
||||||
def _scan_with_trivy(self, db: Session, image: Image) -> List[Vulnerability]:
|
def _scan_with_trivy(self, db: Session, image: Image, scan_job_id: Optional[int] = None) -> List[Vulnerability]:
|
||||||
vulnerabilities = []
|
vulnerabilities = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -54,7 +54,7 @@ class VulnerabilityScanner:
|
|||||||
|
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
data = json.loads(result.stdout)
|
data = json.loads(result.stdout)
|
||||||
vulnerabilities = self._parse_trivy_results(db, image, data)
|
vulnerabilities = self._parse_trivy_results(db, image, data, scan_job_id)
|
||||||
else:
|
else:
|
||||||
print(f"Trivy scan failed for {image.full_image_name}: {result.stderr}")
|
print(f"Trivy scan failed for {image.full_image_name}: {result.stderr}")
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ class VulnerabilityScanner:
|
|||||||
return vulnerabilities
|
return vulnerabilities
|
||||||
|
|
||||||
def _parse_trivy_results(
|
def _parse_trivy_results(
|
||||||
self, db: Session, image: Image, data: Dict
|
self, db: Session, image: Image, data: Dict, scan_job_id: Optional[int] = None
|
||||||
) -> List[Vulnerability]:
|
) -> List[Vulnerability]:
|
||||||
vulnerabilities = []
|
vulnerabilities = []
|
||||||
|
|
||||||
@@ -72,14 +72,14 @@ class VulnerabilityScanner:
|
|||||||
for result in results:
|
for result in results:
|
||||||
vulns = result.get("Vulnerabilities", [])
|
vulns = result.get("Vulnerabilities", [])
|
||||||
for vuln in vulns:
|
for vuln in vulns:
|
||||||
vulnerability = self._create_vulnerability_from_trivy(db, image, vuln)
|
vulnerability = self._create_vulnerability_from_trivy(db, image, vuln, scan_job_id)
|
||||||
if vulnerability:
|
if vulnerability:
|
||||||
vulnerabilities.append(vulnerability)
|
vulnerabilities.append(vulnerability)
|
||||||
|
|
||||||
return vulnerabilities
|
return vulnerabilities
|
||||||
|
|
||||||
def _create_vulnerability_from_trivy(
|
def _create_vulnerability_from_trivy(
|
||||||
self, db: Session, image: Image, vuln_data: Dict
|
self, db: Session, image: Image, vuln_data: Dict, scan_job_id: Optional[int] = None
|
||||||
) -> Optional[Vulnerability]:
|
) -> Optional[Vulnerability]:
|
||||||
vulnerability_id = vuln_data.get("VulnerabilityID")
|
vulnerability_id = vuln_data.get("VulnerabilityID")
|
||||||
if not vulnerability_id:
|
if not vulnerability_id:
|
||||||
@@ -97,6 +97,8 @@ class VulnerabilityScanner:
|
|||||||
if existing:
|
if existing:
|
||||||
existing.is_active = True
|
existing.is_active = True
|
||||||
existing.scan_date = datetime.utcnow()
|
existing.scan_date = datetime.utcnow()
|
||||||
|
if scan_job_id:
|
||||||
|
existing.scan_job_id = scan_job_id
|
||||||
db.commit()
|
db.commit()
|
||||||
return existing
|
return existing
|
||||||
|
|
||||||
@@ -128,6 +130,7 @@ class VulnerabilityScanner:
|
|||||||
|
|
||||||
vulnerability = Vulnerability(
|
vulnerability = Vulnerability(
|
||||||
image_id=image.id,
|
image_id=image.id,
|
||||||
|
scan_job_id=scan_job_id,
|
||||||
vulnerability_id=vulnerability_id,
|
vulnerability_id=vulnerability_id,
|
||||||
severity=severity,
|
severity=severity,
|
||||||
title=title,
|
title=title,
|
||||||
@@ -144,7 +147,7 @@ class VulnerabilityScanner:
|
|||||||
|
|
||||||
return vulnerability
|
return vulnerability
|
||||||
|
|
||||||
def _scan_with_api(self, db: Session, image: Image) -> List[Vulnerability]:
|
def _scan_with_api(self, db: Session, image: Image, scan_job_id: Optional[int] = None) -> List[Vulnerability]:
|
||||||
vulnerabilities = []
|
vulnerabilities = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -159,7 +162,7 @@ class VulnerabilityScanner:
|
|||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
vulnerabilities = self._parse_api_results(db, image, data)
|
vulnerabilities = self._parse_api_results(db, image, data, scan_job_id)
|
||||||
else:
|
else:
|
||||||
print(
|
print(
|
||||||
f"API scan failed for {image.full_image_name}: {response.status_code}"
|
f"API scan failed for {image.full_image_name}: {response.status_code}"
|
||||||
@@ -171,21 +174,21 @@ class VulnerabilityScanner:
|
|||||||
return vulnerabilities
|
return vulnerabilities
|
||||||
|
|
||||||
def _parse_api_results(
|
def _parse_api_results(
|
||||||
self, db: Session, image: Image, data: Dict
|
self, db: Session, image: Image, data: Dict, scan_job_id: Optional[int] = None
|
||||||
) -> List[Vulnerability]:
|
) -> List[Vulnerability]:
|
||||||
vulnerabilities = []
|
vulnerabilities = []
|
||||||
|
|
||||||
cves = data.get("vulnerabilities", [])
|
cves = data.get("vulnerabilities", [])
|
||||||
for cve in cves:
|
for cve in cves:
|
||||||
cve_data = cve.get("cve", {})
|
cve_data = cve.get("cve", {})
|
||||||
vulnerability = self._create_vulnerability_from_api(db, image, cve_data)
|
vulnerability = self._create_vulnerability_from_api(db, image, cve_data, scan_job_id)
|
||||||
if vulnerability:
|
if vulnerability:
|
||||||
vulnerabilities.append(vulnerability)
|
vulnerabilities.append(vulnerability)
|
||||||
|
|
||||||
return vulnerabilities
|
return vulnerabilities
|
||||||
|
|
||||||
def _create_vulnerability_from_api(
|
def _create_vulnerability_from_api(
|
||||||
self, db: Session, image: Image, cve_data: Dict
|
self, db: Session, image: Image, cve_data: Dict, scan_job_id: Optional[int] = None
|
||||||
) -> Optional[Vulnerability]:
|
) -> Optional[Vulnerability]:
|
||||||
vulnerability_id = cve_data.get("id")
|
vulnerability_id = cve_data.get("id")
|
||||||
if not vulnerability_id:
|
if not vulnerability_id:
|
||||||
@@ -203,6 +206,8 @@ class VulnerabilityScanner:
|
|||||||
if existing:
|
if existing:
|
||||||
existing.is_active = True
|
existing.is_active = True
|
||||||
existing.scan_date = datetime.utcnow()
|
existing.scan_date = datetime.utcnow()
|
||||||
|
if scan_job_id:
|
||||||
|
existing.scan_job_id = scan_job_id
|
||||||
db.commit()
|
db.commit()
|
||||||
return existing
|
return existing
|
||||||
|
|
||||||
@@ -243,6 +248,7 @@ class VulnerabilityScanner:
|
|||||||
|
|
||||||
vulnerability = Vulnerability(
|
vulnerability = Vulnerability(
|
||||||
image_id=image.id,
|
image_id=image.id,
|
||||||
|
scan_job_id=scan_job_id,
|
||||||
vulnerability_id=vulnerability_id,
|
vulnerability_id=vulnerability_id,
|
||||||
severity=severity,
|
severity=severity,
|
||||||
title=title,
|
title=title,
|
||||||
@@ -280,13 +286,13 @@ class VulnerabilityScanner:
|
|||||||
else:
|
else:
|
||||||
return "unspecified"
|
return "unspecified"
|
||||||
|
|
||||||
def scan_all_images(self, db: Session) -> None:
|
def scan_all_images(self, db: Session, scan_job_id: Optional[int] = None) -> None:
|
||||||
images = db.query(Image).filter(Image.is_active == True).all()
|
images = db.query(Image).filter(Image.is_active == True).all()
|
||||||
|
|
||||||
for image in images:
|
for image in images:
|
||||||
try:
|
try:
|
||||||
print(f"Scanning vulnerabilities for {image.full_image_name}")
|
print(f"Scanning vulnerabilities for {image.full_image_name}")
|
||||||
vulnerabilities = self.scan_image_vulnerabilities(db, image)
|
vulnerabilities = self.scan_image_vulnerabilities(db, image, scan_job_id)
|
||||||
print(f"Found {len(vulnerabilities)} vulnerabilities")
|
print(f"Found {len(vulnerabilities)} vulnerabilities")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error scanning {image.full_image_name}: {e}")
|
print(f"Error scanning {image.full_image_name}: {e}")
|
||||||
|
|||||||
Reference in New Issue
Block a user