mirror of
https://github.com/quay/quay.git
synced 2025-04-18 10:44:06 +03:00
Currently the prometheus and GC workers are not running correctly. Reverting the following commits: -4e1a985e70
-dac183a1ef
-68a0d9eaf0
-af1aacea08
-f334b80098
175 lines
7.1 KiB
Python
175 lines
7.1 KiB
Python
import logging
|
|
import time
|
|
|
|
import features
|
|
|
|
from app import app, storage, secscan_notification_queue, secscan_model, registry_model
|
|
from data.secscan_model.datatypes import PaginatedNotificationStatus
|
|
from notifications import notification_batch
|
|
from workers.queueworker import QueueWorker, JobException
|
|
from util.log import logfile_path
|
|
from util.secscan import PRIORITY_LEVELS
|
|
from workers.gunicorn_worker import GunicornWorker
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
_POLL_PERIOD_SECONDS = 60
|
|
_PROCESSING_SECONDS_EXPIRATION = 60 * 60 # 1 hour
|
|
TAG_LIMIT = 100 # tags
|
|
|
|
|
|
class SecurityScanningNotificationWorker(QueueWorker):
|
|
"""
|
|
Worker which reads queued notifications from the scanning system and emits notifications
|
|
for repositories.
|
|
"""
|
|
|
|
_secscan_model = secscan_model
|
|
|
|
def process_queue_item(self, job_details):
|
|
self._perform_notification_worker(job_details)
|
|
|
|
def _perform_notification_worker(self, job_details):
|
|
"""
|
|
Performs the work for handling a security notification as referenced by the given data
|
|
object.
|
|
|
|
Returns True on successful handling, False on non-retryable failure and raises a
|
|
JobException on retryable failure.
|
|
"""
|
|
|
|
logger.debug("Got security scanning notification queue item: %s", job_details)
|
|
|
|
notification_id = job_details["notification_id"]
|
|
page_index = job_details.get("current_page_index", None)
|
|
while True:
|
|
page_result = self._secscan_model.lookup_notification_page(notification_id, page_index)
|
|
if page_result is None:
|
|
logger.warning("Got unsupported for notification page")
|
|
return
|
|
|
|
logger.debug(
|
|
"Got page result for notification %s: %s", notification_id, page_result.status
|
|
)
|
|
if page_result.status == PaginatedNotificationStatus.RETRYABLE_ERROR:
|
|
logger.warning("Got notification page issue; will retry in the future")
|
|
raise JobException()
|
|
|
|
if page_result.status == PaginatedNotificationStatus.FATAL_ERROR:
|
|
logger.error("Got fatal error for notification %s; terminating", notification_id)
|
|
return
|
|
|
|
# Update the job details with the current page index and extend processing to ensure
|
|
# we do not timeout during the notification handling.
|
|
job_details["current_page_index"] = page_index
|
|
self.extend_processing(_PROCESSING_SECONDS_EXPIRATION, job_details)
|
|
|
|
with notification_batch() as spawn_notification:
|
|
# Process the notification page into notifications.
|
|
for updated_vuln_info in self._secscan_model.process_notification_page(
|
|
page_result.data
|
|
):
|
|
vulnerability = updated_vuln_info.vulnerability
|
|
|
|
# Find all manifests in repositories with configured security notifications that
|
|
# match that of the vulnerability.
|
|
for manifest in registry_model.find_manifests_for_sec_notification(
|
|
updated_vuln_info.manifest_digest
|
|
):
|
|
# Filter any repositories where the notification level is below that of
|
|
# the vulnerability.
|
|
found_severity = PRIORITY_LEVELS.get(
|
|
vulnerability.Severity, PRIORITY_LEVELS["Unknown"]
|
|
)
|
|
|
|
lowest_severity = PRIORITY_LEVELS["Critical"]
|
|
for severity_name in registry_model.lookup_secscan_notification_severities(
|
|
manifest.repository
|
|
):
|
|
severity = PRIORITY_LEVELS.get(
|
|
severity_name,
|
|
PRIORITY_LEVELS["Critical"],
|
|
)
|
|
|
|
if lowest_severity["score"] > severity["score"]:
|
|
lowest_severity = severity
|
|
|
|
if found_severity["score"] < lowest_severity["score"]:
|
|
continue
|
|
|
|
# Issue a notification for the repository.
|
|
tag_names = list(registry_model.tag_names_for_manifest(manifest, TAG_LIMIT))
|
|
if tag_names:
|
|
event_data = {
|
|
"tags": list(tag_names),
|
|
"vulnerability": {
|
|
"id": vulnerability.Name,
|
|
"description": vulnerability.Description,
|
|
"link": vulnerability.Link,
|
|
"priority": found_severity["title"],
|
|
"has_fix": bool(vulnerability.FixedBy),
|
|
},
|
|
}
|
|
|
|
spawn_notification(
|
|
manifest.repository, "vulnerability_found", event_data
|
|
)
|
|
|
|
# Mark the job as having completed the page.
|
|
page_index = page_result.next_page_index
|
|
if page_index is None:
|
|
logger.debug("Completed processing of notification %s", notification_id)
|
|
attempt_count = 5
|
|
while not self._secscan_model.mark_notification_handled(notification_id):
|
|
attempt_count -= 1
|
|
if attempt_count == 0:
|
|
break
|
|
|
|
return
|
|
|
|
job_details["current_page_index"] = page_index
|
|
self.extend_processing(_PROCESSING_SECONDS_EXPIRATION, job_details)
|
|
|
|
|
|
def create_gunicorn_worker():
|
|
"""
|
|
follows the gunicorn application factory pattern, enabling
|
|
a quay worker to run as a gunicorn worker thread.
|
|
|
|
this is useful when utilizing gunicorn's hot reload in local dev.
|
|
|
|
utilizing this method will enforce a 1:1 quay worker to gunicorn worker ratio.
|
|
"""
|
|
feature_flag = features.SECURITY_SCANNER and features.SECURITY_NOTIFICATIONS
|
|
note_worker = SecurityScanningNotificationWorker(
|
|
secscan_notification_queue, poll_period_seconds=_POLL_PERIOD_SECONDS
|
|
)
|
|
worker = GunicornWorker(__name__, app, note_worker, feature_flag)
|
|
return worker
|
|
|
|
|
|
if __name__ == "__main__":
|
|
logging.config.fileConfig(logfile_path(debug=False), disable_existing_loggers=False)
|
|
|
|
if app.config.get("ACCOUNT_RECOVERY_MODE", False):
|
|
logger.debug("Quay running in account recovery mode")
|
|
while True:
|
|
time.sleep(100000)
|
|
|
|
if not features.SECURITY_SCANNER:
|
|
logger.debug("Security scanner disabled; sleeping")
|
|
while True:
|
|
time.sleep(10000)
|
|
|
|
if not features.SECURITY_NOTIFICATIONS:
|
|
logger.debug("Security scanner notifications disabled; sleeping")
|
|
while True:
|
|
time.sleep(10000)
|
|
|
|
logger.debug("Starting security scanning notification worker")
|
|
worker = SecurityScanningNotificationWorker(
|
|
secscan_notification_queue, poll_period_seconds=_POLL_PERIOD_SECONDS
|
|
)
|
|
worker.start()
|