Skip to content

Commit 48b23ee

Browse files
committed
Merge branch 'dev' of https://github.com/MaibornWolff/SecObserve into stackable
2 parents 27a3a57 + a57364e commit 48b23ee

File tree

137 files changed

+1889
-3658
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

137 files changed

+1889
-3658
lines changed

.github/workflows/build_push_dev.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737
-
3838
name: Build and push backend
3939
id: build-and-push-backend
40-
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
40+
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
4141
with:
4242
context: .
4343
file: ./docker/backend/prod/django/Dockerfile
@@ -78,7 +78,7 @@ jobs:
7878
-
7979
name: Build and push frontend
8080
id: build-and-push-frontend
81-
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
81+
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
8282
with:
8383
context: .
8484
file: ./docker/frontend/prod/Dockerfile

.github/workflows/build_push_release.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
run: echo "CREATED=$(date +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_ENV
3737
-
3838
name: Build and push backend
39-
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
39+
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
4040
with:
4141
context: .
4242
file: ./docker/backend/prod/django/Dockerfile
@@ -74,7 +74,7 @@ jobs:
7474
run: echo "CREATED=$(date +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_ENV
7575
-
7676
name: Build and push frontend
77-
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
77+
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
7878
with:
7979
context: .
8080
file: ./docker/frontend/prod/Dockerfile
@@ -98,13 +98,13 @@ jobs:
9898
ref: 'v${{ github.event.inputs.release }}'
9999
-
100100
name: Run vulnerability scanners for images
101-
uses: MaibornWolff/secobserve_actions_templates/actions/vulnerability_scanner@32595f58f393dbc99507c7ec574e47c3443808f1 # main
101+
uses: MaibornWolff/secobserve_actions_templates/actions/vulnerability_scanner@03881bede1d05a40887bf26d8dfd7a1a37be892d # main
102102
with:
103103
so_configuration: 'so_configuration_sca_current.yml'
104104
SO_API_TOKEN: ${{ secrets.SO_API_TOKEN }}
105105
-
106106
name: Run vulnerability scanners for endpoints
107-
uses: MaibornWolff/secobserve_actions_templates/actions/vulnerability_scanner@32595f58f393dbc99507c7ec574e47c3443808f1 # main
107+
uses: MaibornWolff/secobserve_actions_templates/actions/vulnerability_scanner@03881bede1d05a40887bf26d8dfd7a1a37be892d # main
108108
with:
109109
so_configuration: 'so_configuration_endpoints.yml'
110110
SO_API_TOKEN: ${{ secrets.SO_API_TOKEN }}

.github/workflows/check_backend.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
runs-on: ubuntu-latest
1414
steps:
1515
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
16-
- name: Set up Python 3.12
16+
- name: Set up Python
1717
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
1818
with:
1919
python-version: 3.12

.github/workflows/check_licenses_dev.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737
cdxgen ./frontend --type npm --no-babel --required-only --profile license-compliance --no-auto-compositions --project-name secobserve --output sbom_frontend_application.json
3838
-
3939
name: Import backend SBOM
40-
uses: MaibornWolff/secobserve_actions_templates/actions/upload_sbom@32595f58f393dbc99507c7ec574e47c3443808f1 # main
40+
uses: MaibornWolff/secobserve_actions_templates/actions/upload_sbom@03881bede1d05a40887bf26d8dfd7a1a37be892d # main
4141
with:
4242
so_product_name: 'SecObserve'
4343
so_file_name: 'sbom_backend_application.json'
@@ -46,7 +46,7 @@ jobs:
4646
so_api_token: ${{ secrets.SO_API_TOKEN }}
4747
-
4848
name: Import frontend SBOM
49-
uses: MaibornWolff/secobserve_actions_templates/actions/upload_sbom@32595f58f393dbc99507c7ec574e47c3443808f1 # main
49+
uses: MaibornWolff/secobserve_actions_templates/actions/upload_sbom@03881bede1d05a40887bf26d8dfd7a1a37be892d # main
5050
with:
5151
so_product_name: 'SecObserve'
5252
so_file_name: 'sbom_frontend_application.json'

.github/workflows/check_vulnerabilities.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
1515
-
1616
name: Run vulnerability scanners for code
17-
uses: MaibornWolff/secobserve_actions_templates/actions/vulnerability_scanner@32595f58f393dbc99507c7ec574e47c3443808f1 # main
17+
uses: MaibornWolff/secobserve_actions_templates/actions/vulnerability_scanner@03881bede1d05a40887bf26d8dfd7a1a37be892d # main
1818
with:
1919
so_configuration: 'so_configuration_code.yml'
2020
SO_API_TOKEN: ${{ secrets.SO_API_TOKEN }}

.github/workflows/scan_sca_current.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,16 @@ jobs:
1616
name: Checkout
1717
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
1818
with:
19-
ref: 'v1.31.0'
19+
ref: 'v1.32.1'
2020
-
2121
name: Run SCA vulnerability scanners
22-
uses: MaibornWolff/secobserve_actions_templates/actions/vulnerability_scanner@32595f58f393dbc99507c7ec574e47c3443808f1 # main
22+
uses: MaibornWolff/secobserve_actions_templates/actions/vulnerability_scanner@03881bede1d05a40887bf26d8dfd7a1a37be892d # main
2323
with:
2424
so_configuration: 'so_configuration_sca_current.yml'
2525
SO_API_TOKEN: ${{ secrets.SO_API_TOKEN }}
2626
-
2727
name: Run endpoint vulnerability scanners
28-
uses: MaibornWolff/secobserve_actions_templates/actions/vulnerability_scanner@32595f58f393dbc99507c7ec574e47c3443808f1 # main
28+
uses: MaibornWolff/secobserve_actions_templates/actions/vulnerability_scanner@03881bede1d05a40887bf26d8dfd7a1a37be892d # main
2929
with:
3030
so_configuration: 'so_configuration_endpoints.yml'
3131
SO_API_TOKEN: ${{ secrets.SO_API_TOKEN }}

.github/workflows/scorecard.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,6 @@ jobs:
6767

6868
# Upload the results to GitHub's code scanning dashboard.
6969
- name: "Upload to code-scanning"
70-
uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17
70+
uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
7171
with:
7272
sarif_file: results.sarif

backend/application/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "1.31.0"
1+
__version__ = "1.32.1"
22

33
import pymysql
44

backend/application/commons/api/views.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from typing import Union
22

3+
import environ
34
from rest_framework.decorators import action
45
from rest_framework.exceptions import ValidationError
56
from rest_framework.permissions import IsAuthenticated
@@ -66,6 +67,10 @@ def get(self, request: Request) -> Response:
6667
if settings.feature_exploit_information:
6768
features.append("feature_exploit_information")
6869

70+
env = environ.Env()
71+
if env("EMAIL_HOST", default="") or env("EMAIL_PORT", default=""):
72+
features.append("feature_email")
73+
6974
content: dict[str, Union[int, list[str]]] = {
7075
"features": features,
7176
}

backend/application/core/api/serializers_product.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ class Meta:
606606
model = Branch
607607
fields = ["id", "name", "name_with_product"]
608608

609-
def get_name_with_product(self, obj: Service) -> str:
609+
def get_name_with_product(self, obj: Branch) -> str:
610610
return f"{obj.name} ({obj.product.name})"
611611

612612

@@ -618,6 +618,11 @@ class ServiceSerializer(ModelSerializer):
618618
open_low_observation_count = SerializerMethodField()
619619
open_none_observation_count = SerializerMethodField()
620620
open_unknown_observation_count = SerializerMethodField()
621+
forbidden_licenses_count = SerializerMethodField()
622+
review_required_licenses_count = SerializerMethodField()
623+
unknown_licenses_count = SerializerMethodField()
624+
allowed_licenses_count = SerializerMethodField()
625+
ignored_licenses_count = SerializerMethodField()
621626

622627
class Meta:
623628
model = Service
@@ -644,6 +649,32 @@ def get_open_none_observation_count(self, obj: Service) -> int:
644649
def get_open_unknown_observation_count(self, obj: Service) -> int:
645650
return obj.open_unknown_observation_count
646651

652+
def get_forbidden_licenses_count(self, obj: Branch) -> int:
653+
return obj.forbidden_licenses_count
654+
655+
def get_review_required_licenses_count(self, obj: Branch) -> int:
656+
return obj.review_required_licenses_count
657+
658+
def get_unknown_licenses_count(self, obj: Branch) -> int:
659+
return obj.unknown_licenses_count
660+
661+
def get_allowed_licenses_count(self, obj: Branch) -> int:
662+
return obj.allowed_licenses_count
663+
664+
def get_ignored_licenses_count(self, obj: Branch) -> int:
665+
return obj.ignored_licenses_count
666+
667+
668+
class ServiceNameSerializer(ModelSerializer):
669+
name_with_product = SerializerMethodField()
670+
671+
class Meta:
672+
model = Branch
673+
fields = ["id", "name", "name_with_product"]
674+
675+
def get_name_with_product(self, obj: Service) -> str:
676+
return f"{obj.name} ({obj.product.name})"
677+
647678

648679
class PURLTypeElementSerializer(Serializer):
649680
id = CharField()

backend/application/core/api/views.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
ProductSerializer,
7676
PURLTypeElementSerializer,
7777
PURLTypeSerializer,
78+
ServiceNameSerializer,
7879
ServiceSerializer,
7980
)
8081
from application.core.models import (
@@ -471,6 +472,18 @@ def get_queryset(self) -> QuerySet[Service]:
471472
return get_services().select_related("product")
472473

473474

475+
class ServiceNameViewSet(GenericViewSet, ListModelMixin, RetrieveModelMixin):
476+
serializer_class = ServiceNameSerializer
477+
filterset_class = ServiceFilter
478+
permission_classes = (IsAuthenticated, UserHasServicePermission)
479+
queryset = Service.objects.none()
480+
filter_backends = [SearchFilter, DjangoFilterBackend]
481+
search_fields = ["name"]
482+
483+
def get_queryset(self) -> QuerySet[Service]:
484+
return get_services().select_related("product")
485+
486+
474487
class ObservationViewSet(ModelViewSet):
475488
serializer_class = ObservationSerializer
476489
filterset_class = ObservationFilter

backend/application/core/models.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,51 @@ def open_unknown_observation_count(self) -> int:
330330
current_status=Status.STATUS_OPEN,
331331
).count()
332332

333+
@property
334+
def forbidden_licenses_count(self) -> int:
335+
License_Component = apps.get_model("licenses", "License_Component")
336+
return License_Component.objects.filter(
337+
origin_service=self,
338+
branch=self.product.repository_default_branch,
339+
evaluation_result=License_Policy_Evaluation_Result.RESULT_FORBIDDEN,
340+
).count()
341+
342+
@property
343+
def review_required_licenses_count(self) -> int:
344+
License_Component = apps.get_model("licenses", "License_Component")
345+
return License_Component.objects.filter(
346+
origin_service=self,
347+
branch=self.product.repository_default_branch,
348+
evaluation_result=License_Policy_Evaluation_Result.RESULT_REVIEW_REQUIRED,
349+
).count()
350+
351+
@property
352+
def unknown_licenses_count(self) -> int:
353+
License_Component = apps.get_model("licenses", "License_Component")
354+
return License_Component.objects.filter(
355+
origin_service=self,
356+
branch=self.product.repository_default_branch,
357+
evaluation_result=License_Policy_Evaluation_Result.RESULT_UNKNOWN,
358+
).count()
359+
360+
@property
361+
def allowed_licenses_count(self) -> int:
362+
License_Component = apps.get_model("licenses", "License_Component")
363+
return License_Component.objects.filter(
364+
origin_service=self,
365+
branch=self.product.repository_default_branch,
366+
evaluation_result=License_Policy_Evaluation_Result.RESULT_ALLOWED,
367+
).count()
368+
369+
@property
370+
def ignored_licenses_count(self) -> int:
371+
License_Component = apps.get_model("licenses", "License_Component")
372+
return License_Component.objects.filter(
373+
origin_service=self,
374+
branch=self.product.repository_default_branch,
375+
evaluation_result=License_Policy_Evaluation_Result.RESULT_IGNORED,
376+
).count()
377+
333378

334379
class Product_Member(Model):
335380
product = ForeignKey(Product, on_delete=CASCADE)

backend/application/core/queries/observation.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,31 @@ def get_observations_for_vulnerability_check(
6767
branch: Optional[Branch],
6868
filename: str,
6969
api_configuration_name: str,
70+
service: Optional[str],
7071
) -> QuerySet[Observation]:
72+
if filename or api_configuration_name:
73+
return Observation.objects.filter(
74+
product=product,
75+
branch=branch,
76+
upload_filename=filename,
77+
api_configuration_name=api_configuration_name,
78+
)
79+
80+
if service:
81+
return Observation.objects.filter(
82+
product=product,
83+
branch=branch,
84+
upload_filename="",
85+
api_configuration_name="",
86+
origin_service__name=service,
87+
)
88+
7189
return Observation.objects.filter(
7290
product=product,
7391
branch=branch,
74-
upload_filename=filename,
75-
api_configuration_name=api_configuration_name,
92+
upload_filename="",
93+
api_configuration_name="",
94+
origin_service__isnull=True,
7695
)
7796

7897

backend/application/import_observations/api/serializers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,14 @@ class FileUploadSBOMByIdRequestSerializer(Serializer):
4949
file = FileField(max_length=255)
5050
product = IntegerField(validators=[MinValueValidator(0)])
5151
branch = IntegerField(validators=[MinValueValidator(0)], required=False)
52+
service = CharField(max_length=255, required=False)
5253

5354

5455
class FileUploadSBOMByNameRequestSerializer(Serializer):
5556
file = FileField(max_length=255)
5657
product_name = CharField(max_length=255)
5758
branch_name = CharField(max_length=255, required=False)
59+
service = CharField(max_length=255, required=False)
5860

5961

6062
class ApiImportObservationsByIdRequestSerializer(Serializer):

backend/application/import_observations/api/views.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,12 +331,13 @@ def _file_upload_observations(
331331

332332
def _file_upload_sbom(request_serializer: Serializer, product: Product, branch: Optional[Branch]) -> dict[str, int]:
333333
file = request_serializer.validated_data.get("file")
334+
service = request_serializer.validated_data.get("service")
334335

335336
file_upload_parameters = FileUploadParameters(
336337
product=product,
337338
branch=branch,
338339
file=file,
339-
service="",
340+
service=service,
340341
docker_image_name_tag="",
341342
endpoint_url="",
342343
kubernetes_cluster="",

backend/application/import_observations/parsers/azure_defender/parser.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ def check_format(self, data: list[dict]) -> bool:
2828
return True
2929
return False
3030

31-
def get_observations(self, data: list[dict], product: Product, branch: Optional[Branch]) -> list[Observation]:
31+
def get_observations(
32+
self, data: list[dict], product: Product, branch: Optional[Branch]
33+
) -> tuple[list[Observation], str]:
3234
observations = []
3335

3436
for row in data:
@@ -64,7 +66,7 @@ def get_observations(self, data: list[dict], product: Product, branch: Optional[
6466

6567
observations.append(observation)
6668

67-
return observations
69+
return observations, self.get_name()
6870

6971
def format_markdown(self, string: str) -> str:
7072
string = self.replace_string_with_newlines(string, r"\.[A-Z]")

backend/application/import_observations/parsers/base_parser.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ def get_name(cls) -> str:
1414
def get_type(cls) -> str:
1515
raise NotImplementedError("get_type() must be overridden")
1616

17-
def get_observations(self, data: Any, product: Product, branch: Optional[Branch]) -> list[Observation]:
17+
def get_observations(self, data: Any, product: Product, branch: Optional[Branch]) -> tuple[list[Observation], str]:
1818
raise NotImplementedError("get_observations() must be overridden")
1919

20-
def get_license_components(self, data: Any) -> list[License_Component]: # pylint: disable=unused-argument
20+
def get_license_components(
21+
self, data: Any # pylint: disable=unused-argument
22+
) -> tuple[list[License_Component], str]:
2123
# data is used in the child classes
22-
return []
24+
return [], ""
2325

2426
def get_int_or_none(self, value: Optional[str]) -> int | None:
2527
if value:

backend/application/import_observations/parsers/cryptolyzer/parser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def check_format(self, data: Any) -> bool:
137137
return True
138138
return False
139139

140-
def get_observations(self, data: dict, product: Product, branch: Optional[Branch]) -> list[Observation]:
140+
def get_observations(self, data: dict, product: Product, branch: Optional[Branch]) -> tuple[list[Observation], str]:
141141
observations = []
142142

143143
observation = self.check_weak_protocols(data)
@@ -159,7 +159,7 @@ def get_observations(self, data: dict, product: Product, branch: Optional[Branch
159159
if observation:
160160
observations.append(observation)
161161

162-
return observations
162+
return observations, self.get_name()
163163

164164
def check_weak_protocols(self, data: dict) -> Optional[Observation]:
165165
endpoint_url = self.get_endpoint_url(data.get("versions", {}).get("target", {}))

0 commit comments

Comments
 (0)