Skip to content

Commit f13b087

Browse files
authored
[cravex2-reachability] Implement rule tools triaging vulnerabilities (#560)
Signed-off-by: tdruez <tdruez@aboutcode.org>
1 parent c3399c8 commit f13b087

53 files changed

Lines changed: 4519 additions & 182 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<div>
2+
{% if vulnerability.aliases %}
3+
<div class='mb-2'>
4+
<strong>Aliases</strong>
5+
<div>
6+
{% for alias in vulnerability.aliases %}
7+
<div>
8+
{% if alias|slice:':3' == 'CVE' %}
9+
<a href='https://nvd.nist.gov/vuln/detail/{{ alias }}' target='_blank'>{{ alias }}</a>
10+
{% elif alias|slice:':4' == 'GHSA' %}
11+
<a href='https://github.com/advisories/{{ alias }}' target='_blank'>{{ alias }}</a>
12+
{% elif alias|slice:':3' == 'NPM' %}
13+
<a href='https://github.com/nodejs/security-wg/blob/main/vuln/npm/{{ alias|slice:"4:" }}.json' target='_blank'>{{ alias }}</a>
14+
{% else %}
15+
{{ alias }}
16+
{% endif %}
17+
</div>
18+
{% endfor %}
19+
</div>
20+
</div>
21+
{% endif %}
22+
{% if vulnerability.summary %}
23+
<div class='mb-2'>
24+
<strong>Summary</strong>
25+
<div>{{ vulnerability.summary|linebreaksbr }}</div>
26+
</div>
27+
{% endif %}
28+
{% if vulnerability.exploitability %}
29+
<div class='mb-1'><strong>Exploitability:</strong> {{ vulnerability.get_exploitability_display }}</div>
30+
{% endif %}
31+
{% if vulnerability.risk_score %}
32+
<div><strong>Risk:</strong> {{ vulnerability.risk_score }}</div>
33+
{% endif %}
34+
</div>

dejacode/settings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ def gettext_noop(s):
345345
"policy",
346346
"notification",
347347
"vulnerabilities",
348+
"vulnerabilities.triage",
348349
]
349350

350351
EXTRA_APPS = env.list("EXTRA_APPS", default=[])
@@ -478,6 +479,7 @@ def gettext_noop(s):
478479
hourly = "0 * * * *"
479480
DEJACODE_VULNERABILITIES_CRON = env.str("DEJACODE_VULNERABILITIES_CRON", default=daily_at_3am)
480481
DEJACODE_POLICY_RULES_CRON = env.str("DEJACODE_POLICY_RULES_CRON", default=hourly)
482+
DEJACODE_VULNERABILITY_TRIAGE_CRON = env.str("DEJACODE_VULNERABILITY_TRIAGE_CRON", default=hourly)
481483

482484

483485
def enable_rq_eager_mode():

dejacode/static/css/dejacode_bootstrap.css

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -414,10 +414,17 @@ table.vulnerabilities-table .column-summary {
414414

415415
/* -- Vulnerability tab -- */
416416
#tab_vulnerabilities .column-advisory_uid {
417-
width: 230px;
417+
width: 240px;
418418
}
419419
#tab_vulnerabilities .column-affected_packages {
420-
min-width: 250px;
420+
min-width: 300px;
421+
width: 300px;
422+
}
423+
#tab_vulnerabilities .column-triage_action {
424+
min-width: 165px;
425+
}
426+
#tab_vulnerabilities .column-action {
427+
width: 30px;
421428
}
422429
#tab_vulnerabilities .column-exploitability {
423430
width: 140px;
@@ -433,13 +440,13 @@ table.vulnerabilities-table .column-summary {
433440
width: 300px;
434441
}
435442
#tab_vulnerabilities .column-vulnerability_analyses__state {
436-
min-width: 125px;
443+
min-width: 100px;
437444
}
438445
#tab_vulnerabilities .column-vulnerability_analyses__justification {
439446
min-width: 130px;
440447
}
441448
#tab_vulnerabilities .column-vulnerability_analyses__responses {
442-
width: 185px;
449+
min-width: 120px;
443450
}
444451
#tab_vulnerabilities .column-vulnerability_analyses__is_reachable {
445452
width: 80px;
@@ -1072,3 +1079,10 @@ select.bootstrap-select-filter {
10721079
position: absolute;
10731080
z-index: 1;
10741081
}
1082+
.popover.wide-popover {
1083+
--bs-popover-max-width: 420px;
1084+
max-width: 420px;
1085+
}
1086+
.stretch-cell {
1087+
height: 1px;
1088+
}

dje/cron_jobs.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,28 @@
1212

1313
from dje.tasks import update_vulnerabilities
1414
from policy.tasks import evaluate_all_products_rules_task
15+
from vulnerabilities.triage.tasks import evaluate_all_products_vulnerability_triage_task
1516

16-
two_hour = 7200
17+
two_hours = 7200
18+
ten_minutes = 600
1719

1820
cron.register(
1921
func=update_vulnerabilities,
2022
queue_name="default",
2123
cron=settings.DEJACODE_VULNERABILITIES_CRON, # Daily at 3am by default
22-
job_timeout=two_hour,
24+
job_timeout=two_hours,
2325
)
2426

2527
cron.register(
2628
func=evaluate_all_products_rules_task,
2729
queue_name="default",
2830
cron=settings.DEJACODE_POLICY_RULES_CRON, # Hourly by default
29-
job_timeout=two_hour,
31+
job_timeout=two_hours,
32+
)
33+
34+
cron.register(
35+
func=evaluate_all_products_vulnerability_triage_task,
36+
queue_name="default",
37+
cron=settings.DEJACODE_VULNERABILITY_TRIAGE_CRON, # Hourly by default
38+
job_timeout=ten_minutes,
3039
)

dje/tasks.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ def scancodeio_submit_scan(uris, user_uuid, dataspace_uuid):
141141
def update_vulnerabilities():
142142
"""Fetch vulnerabilities for all Dataspaces that enable vulnerablecodedb access."""
143143
from vulnerabilities.fetch import fetch_from_vulnerablecode
144+
from vulnerabilities.triage.tasks import evaluate_all_products_vulnerability_triage_task
144145

145146
logger.info("Entering update_vulnerabilities task")
146147
Dataspace = apps.get_model("dje", "Dataspace")
@@ -155,3 +156,6 @@ def update_vulnerabilities():
155156
timeout=60,
156157
log_func=logger.debug,
157158
)
159+
160+
logger.info("Vulnerability fetch complete. Enqueuing triage evaluation.")
161+
evaluate_all_products_vulnerability_triage_task.delay()

dje/templates/tabs/pagination.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
{% endif %}
2020
</div>
2121
</li>
22+
{% if extra_nav_item_template %}
23+
{% include extra_nav_item_template %}
24+
{% endif %}
2225
</ul>
2326
<div class="mt-1">
2427
{% include 'includes/filters_breadcrumbs.html' with filterset=filterset fragment=tab_id only %}

dje/tests/test_forms.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def test_tabs_permission_formset_load_perms(self):
110110

111111
def test_copy_defaults_form_get_all_dataspaced_models(self):
112112
dataspaced_models = CopyDefaultsForm.get_all_dataspaced_models()
113-
self.assertEqual(10, len(dataspaced_models))
113+
self.assertEqual(11, len(dataspaced_models))
114114
self.assertEqual(7, len(dataspaced_models.get("Component Catalog")))
115115
self.assertIn("Subcomponent", str(dataspaced_models.get("Component Catalog")))
116116

policy/signals.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,9 @@ def evaluate_product_rules_on_product_save(sender, instance, **kwargs):
2626
evaluate_product_rules_task.delay(product_uuid=instance.uuid)
2727

2828

29-
@receiver(post_save, sender="product_portfolio.ProductPackage")
30-
def evaluate_product_rules_on_productpackage_save(sender, instance, **kwargs):
31-
"""Queue a policy rule evaluation whenever a package is added or updated in a product."""
32-
evaluate_product_rules_task.delay(product_uuid=instance.product.uuid)
33-
34-
35-
@receiver(post_delete, sender="product_portfolio.ProductPackage")
36-
def evaluate_product_rules_on_productpackage_delete(sender, instance, **kwargs):
37-
"""Queue a policy rule evaluation whenever a package is removed from a product."""
29+
@receiver([post_save, post_delete], sender="product_portfolio.ProductPackage")
30+
def evaluate_product_rules_on_productpackage_change(sender, instance, **kwargs):
31+
"""Queue a policy rule evaluation whenever a package is added, updated, or removed."""
3832
evaluate_product_rules_task.delay(product_uuid=instance.product.uuid)
3933

4034

policy/tasks.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ def evaluate_product_rules_task(product_uuid):
3333
logger.error(f"evaluate_product_rules_task: product {product_uuid} not found, skipping.")
3434
return
3535

36-
logger.info(f"Evaluating policy rules for product {product}")
36+
logger.info(f"Evaluating policy rules for product id={product.id}")
3737
new_violations, resolved_count = evaluate_rules(product)
3838
logger.info(
39-
f"Policy rules evaluated for {product}: "
39+
f"Policy rules evaluated for product id={product.id}: "
4040
f"{len(new_violations)} new violation(s), {resolved_count} resolved."
4141
)
4242

@@ -59,14 +59,16 @@ def evaluate_all_products_rules_task(include_locked=False, product_uuids=None):
5959
logger.info(f"Starting policy rule evaluation for {count} product(s).")
6060

6161
for product in products:
62-
logger.info(f"Evaluating policy rules for product {product}")
62+
logger.info(f"Evaluating policy rules for product id={product.id}")
6363
try:
6464
new_violations, resolved_count = evaluate_rules(product)
6565
except Exception:
66-
logger.exception(f"Policy rule evaluation failed for product {product}, skipping.")
66+
logger.exception(
67+
f"Policy rule evaluation failed for product id={product.id}, skipping."
68+
)
6769
continue
6870
logger.info(
69-
f"Policy rules evaluated for {product}: "
71+
f"Policy rules evaluated for product id={product.id}: "
7072
f"{len(new_violations)} new violation(s), {resolved_count} resolved."
7173
)
7274

product_portfolio/admin.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@
5959
from product_portfolio.importers import CodebaseResourceImporter
6060
from product_portfolio.importers import ProductComponentImporter
6161
from product_portfolio.importers import ProductPackageImporter
62+
from product_portfolio.importers import paused_product_package_reevaluation
63+
from product_portfolio.importers import reevaluate_products
6264
from product_portfolio.inlines import CodebaseResourceUsageDeployedFromInline
6365
from product_portfolio.inlines import CodebaseResourceUsageDeployedToInline
6466
from product_portfolio.inlines import ProductComponentInline
@@ -72,6 +74,7 @@
7274
from product_portfolio.models import ProductRelationStatus
7375
from product_portfolio.models import ProductStatus
7476
from reporting.filters import ReportingQueryListFilter
77+
from vulnerabilities.triage.models import ProductTriageRuleset
7578

7679

7780
@admin.register(ProductStatus, site=dejacode_site)
@@ -240,12 +243,14 @@ def save_model(self, request, obj, form, change):
240243
if "_saveasnew" in request.POST:
241244
old_product_id = request.resolver_match.kwargs.get("object_id")
242245
old_product = self.get_object(request, old_product_id)
243-
for model_class in [ProductComponent, ProductPackage]:
244-
for relationship in model_class.objects.filter(product=old_product):
245-
relationship.id = None
246-
relationship.uuid = uuid.uuid4()
247-
relationship.product = obj
248-
relationship.save()
246+
with paused_product_package_reevaluation():
247+
for model_class in [ProductComponent, ProductPackage, ProductTriageRuleset]:
248+
for relationship in model_class.objects.filter(product=old_product):
249+
relationship.id = None
250+
relationship.uuid = uuid.uuid4()
251+
relationship.product = obj
252+
relationship.save()
253+
reevaluate_products([obj])
249254

250255
def get_obj_perms_user_select_form(self, request):
251256
"""

0 commit comments

Comments
 (0)