From c159a28016d992f1f703a68c1f83f69215bed450 Mon Sep 17 00:00:00 2001 From: Pepe Fagoaga Date: Thu, 30 Jan 2025 17:16:18 +0545 Subject: [PATCH 01/31] fix(neptune): correct service name (#6743) --- .../neptune_cluster_iam_authentication_enabled.metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prowler/providers/aws/services/neptune/neptune_cluster_iam_authentication_enabled/neptune_cluster_iam_authentication_enabled.metadata.json b/prowler/providers/aws/services/neptune/neptune_cluster_iam_authentication_enabled/neptune_cluster_iam_authentication_enabled.metadata.json index 5be9fa6c4f..c6e29617f0 100644 --- a/prowler/providers/aws/services/neptune/neptune_cluster_iam_authentication_enabled/neptune_cluster_iam_authentication_enabled.metadata.json +++ b/prowler/providers/aws/services/neptune/neptune_cluster_iam_authentication_enabled/neptune_cluster_iam_authentication_enabled.metadata.json @@ -3,7 +3,7 @@ "CheckID": "neptune_cluster_iam_authentication_enabled", "CheckTitle": "Check if Neptune Clusters have IAM authentication enabled.", "CheckType": [], - "ServiceName": "rds", + "ServiceName": "neptune", "SubServiceName": "", "ResourceIdTemplate": "arn:aws:rds:region:account-id:db-cluster", "Severity": "medium", From 5061da6897943408cbf98a4183cfc26294411419 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20Poyatos?= Date: Thu, 30 Jan 2025 13:31:43 +0100 Subject: [PATCH 02/31] feat(findings): Improve /findings/metadata performance (#6748) --- api/poetry.lock | 55 ++++++++- api/pyproject.toml | 3 +- api/src/backend/api/db_router.py | 8 +- api/src/backend/api/filters.py | 47 ++++---- api/src/backend/api/specs/v1.yaml | 141 +----------------------- api/src/backend/api/tests/test_views.py | 32 +++--- api/src/backend/api/v1/serializers.py | 3 +- api/src/backend/api/v1/urls.py | 22 ++-- api/src/backend/api/v1/views.py | 85 +++++++------- api/src/backend/config/django/devel.py | 6 + api/src/backend/tasks/jobs/scan.py | 8 +- 11 files changed, 178 insertions(+), 232 deletions(-) diff --git a/api/poetry.lock b/api/poetry.lock index ec24e9da8d..491b06933a 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -252,6 +252,20 @@ files = [ [package.dependencies] cryptography = "*" +[[package]] +name = "autopep8" +version = "2.3.2" +description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" +optional = false +python-versions = ">=3.9" +files = [ + {file = "autopep8-2.3.2-py2.py3-none-any.whl", hash = "sha256:ce8ad498672c845a0c3de2629c15b635ec2b05ef8177a6e7c91c74f3e9b51128"}, + {file = "autopep8-2.3.2.tar.gz", hash = "sha256:89440a4f969197b69a995e4ce0661b031f455a9f776d2c5ba3dbd83466931758"}, +] + +[package.dependencies] +pycodestyle = ">=2.12.0" + [[package]] name = "awsipranges" version = "0.3.3" @@ -1488,6 +1502,23 @@ docs = ["Sphinx (==2.2.0)", "docutils (<0.18)", "sphinx-rtd-theme (==0.4.3)"] publish = ["build (==0.7.0)", "twine (==3.7.1)"] test = ["coveralls (==3.3.0)", "dj-database-url (==0.5.0)", "freezegun (==1.1.0)", "psycopg2 (>=2.8.4,<3.0.0)", "pytest (==6.2.5)", "pytest-benchmark (==3.4.1)", "pytest-cov (==3.0.0)", "pytest-django (==4.4.0)", "pytest-freezegun (==0.4.2)", "pytest-lazy-fixture (==0.6.3)", "snapshottest (==0.6.0)", "tox (==3.24.4)"] +[[package]] +name = "django-silk" +version = "5.3.2" +description = "Silky smooth profiling for the Django Framework" +optional = false +python-versions = ">=3.9" +files = [ + {file = "django_silk-5.3.2-py3-none-any.whl", hash = "sha256:49f1caebfda28b1707f0cfef524e0476beb82b8c5e40f5ccff7f73a6b4f6d3ac"}, + {file = "django_silk-5.3.2.tar.gz", hash = "sha256:b0db54eebedb8d16f572321bd6daccac0bd3f547ae2618bb45d96fe8fc02229d"}, +] + +[package.dependencies] +autopep8 = "*" +Django = ">=4.2" +gprof2dot = ">=2017.09.19" +sqlparse = "*" + [[package]] name = "django-timezone-field" version = "7.1" @@ -1984,6 +2015,17 @@ protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4 [package.extras] grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] +[[package]] +name = "gprof2dot" +version = "2024.6.6" +description = "Generate a dot graph from the output of several profilers." +optional = false +python-versions = ">=3.8" +files = [ + {file = "gprof2dot-2024.6.6-py2.py3-none-any.whl", hash = "sha256:45b14ad7ce64e299c8f526881007b9eb2c6b75505d5613e96e66ee4d5ab33696"}, + {file = "gprof2dot-2024.6.6.tar.gz", hash = "sha256:fa1420c60025a9eb7734f65225b4da02a10fc6dd741b37fa129bc6b41951e5ab"}, +] + [[package]] name = "grapheme" version = "0.6.0" @@ -3603,6 +3645,17 @@ files = [ [package.dependencies] pyasn1 = ">=0.4.6,<0.7.0" +[[package]] +name = "pycodestyle" +version = "2.12.1" +description = "Python style guide checker" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"}, + {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"}, +] + [[package]] name = "pycparser" version = "2.22" @@ -5070,4 +5123,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.11,<3.13" -content-hash = "af973deaabe32ea4179c44ae702f6a8cc359aabe6f1641dcf2b05534b0bbb3d5" +content-hash = "6465edb36efd1fa6db06d4103fea8046951acc3f4f8b357facaaa34ae2bc74bd" diff --git a/api/pyproject.toml b/api/pyproject.toml index 74d213b0fd..3c7338eb53 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -8,7 +8,7 @@ description = "Prowler's API (Django/DRF)" license = "Apache-2.0" name = "prowler-api" package-mode = false -version = "1.3.1" +version = "1.3.2" [tool.poetry.dependencies] celery = {extras = ["pytest"], version = "^5.4.0"} @@ -37,6 +37,7 @@ uuid6 = "2024.7.10" [tool.poetry.group.dev.dependencies] bandit = "1.7.9" coverage = "7.5.4" +django-silk = "5.3.2" docker = "7.1.0" freezegun = "1.5.1" mypy = "1.10.1" diff --git a/api/src/backend/api/db_router.py b/api/src/backend/api/db_router.py index f3a986ac20..939672f88e 100644 --- a/api/src/backend/api/db_router.py +++ b/api/src/backend/api/db_router.py @@ -4,13 +4,17 @@ class MainRouter: def db_for_read(self, model, **hints): # noqa: F841 model_table_name = model._meta.db_table - if model_table_name.startswith("django_"): + if model_table_name.startswith("django_") or model_table_name.startswith( + "silk_" + ): return self.admin_db return None def db_for_write(self, model, **hints): # noqa: F841 model_table_name = model._meta.db_table - if model_table_name.startswith("django_"): + if model_table_name.startswith("django_") or model_table_name.startswith( + "silk_" + ): return self.admin_db return None diff --git a/api/src/backend/api/filters.py b/api/src/backend/api/filters.py index 1042c3aa86..0ace11fe9c 100644 --- a/api/src/backend/api/filters.py +++ b/api/src/backend/api/filters.py @@ -319,26 +319,27 @@ class FindingFilter(FilterSet): field_name="resources__type", lookup_expr="icontains" ) - resource_tag_key = CharFilter(field_name="resources__tags__key") - resource_tag_key__in = CharInFilter( - field_name="resources__tags__key", lookup_expr="in" - ) - resource_tag_key__icontains = CharFilter( - field_name="resources__tags__key", lookup_expr="icontains" - ) - resource_tag_value = CharFilter(field_name="resources__tags__value") - resource_tag_value__in = CharInFilter( - field_name="resources__tags__value", lookup_expr="in" - ) - resource_tag_value__icontains = CharFilter( - field_name="resources__tags__value", lookup_expr="icontains" - ) - resource_tags = CharInFilter( - method="filter_resource_tag", - lookup_expr="in", - help_text="Filter by resource tags `key:value` pairs.\nMultiple values may be " - "separated by commas.", - ) + # Temporarily disabled until we implement tag filtering in the UI + # resource_tag_key = CharFilter(field_name="resources__tags__key") + # resource_tag_key__in = CharInFilter( + # field_name="resources__tags__key", lookup_expr="in" + # ) + # resource_tag_key__icontains = CharFilter( + # field_name="resources__tags__key", lookup_expr="icontains" + # ) + # resource_tag_value = CharFilter(field_name="resources__tags__value") + # resource_tag_value__in = CharInFilter( + # field_name="resources__tags__value", lookup_expr="in" + # ) + # resource_tag_value__icontains = CharFilter( + # field_name="resources__tags__value", lookup_expr="icontains" + # ) + # resource_tags = CharInFilter( + # method="filter_resource_tag", + # lookup_expr="in", + # help_text="Filter by resource tags `key:value` pairs.\nMultiple values may be " + # "separated by commas.", + # ) scan = UUIDFilter(method="filter_scan_id") scan__in = UUIDInFilter(method="filter_scan_id_in") @@ -374,12 +375,6 @@ class FindingFilter(FilterSet): }, } - @property - def qs(self): - # Force distinct results to prevent duplicates with many-to-many relationships - parent_qs = super().qs - return parent_qs.distinct() - # Convert filter values to UUIDv7 values for use with partitioning def filter_scan_id(self, queryset, name, value): try: diff --git a/api/src/backend/api/specs/v1.yaml b/api/src/backend/api/specs/v1.yaml index cb355b1aa7..7448843b54 100644 --- a/api/src/backend/api/specs/v1.yaml +++ b/api/src/backend/api/specs/v1.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: Prowler API - version: 1.3.1 + version: 1.3.2 description: |- Prowler API specification. @@ -478,51 +478,6 @@ paths: description: Multiple values may be separated by commas. explode: false style: form - - in: query - name: filter[resource_tag_key] - schema: - type: string - - in: query - name: filter[resource_tag_key__icontains] - schema: - type: string - - in: query - name: filter[resource_tag_key__in] - schema: - type: array - items: - type: string - description: Multiple values may be separated by commas. - explode: false - style: form - - in: query - name: filter[resource_tag_value] - schema: - type: string - - in: query - name: filter[resource_tag_value__icontains] - schema: - type: string - - in: query - name: filter[resource_tag_value__in] - schema: - type: array - items: - type: string - description: Multiple values may be separated by commas. - explode: false - style: form - - in: query - name: filter[resource_tags] - schema: - type: array - items: - type: string - description: |- - Filter by resource tags `key:value` pairs. - Multiple values may be separated by commas. - explode: false - style: form - in: query name: filter[resource_type] schema: @@ -1028,51 +983,6 @@ paths: description: Multiple values may be separated by commas. explode: false style: form - - in: query - name: filter[resource_tag_key] - schema: - type: string - - in: query - name: filter[resource_tag_key__icontains] - schema: - type: string - - in: query - name: filter[resource_tag_key__in] - schema: - type: array - items: - type: string - description: Multiple values may be separated by commas. - explode: false - style: form - - in: query - name: filter[resource_tag_value] - schema: - type: string - - in: query - name: filter[resource_tag_value__icontains] - schema: - type: string - - in: query - name: filter[resource_tag_value__in] - schema: - type: array - items: - type: string - description: Multiple values may be separated by commas. - explode: false - style: form - - in: query - name: filter[resource_tags] - schema: - type: array - items: - type: string - description: |- - Filter by resource tags `key:value` pairs. - Multiple values may be separated by commas. - explode: false - style: form - in: query name: filter[resource_type] schema: @@ -1280,7 +1190,6 @@ paths: - services - regions - resource_types - - tags description: endpoint return only specific fields in the response on a per-type basis by including a fields[TYPE] query parameter. explode: false @@ -1498,51 +1407,6 @@ paths: description: Multiple values may be separated by commas. explode: false style: form - - in: query - name: filter[resource_tag_key] - schema: - type: string - - in: query - name: filter[resource_tag_key__icontains] - schema: - type: string - - in: query - name: filter[resource_tag_key__in] - schema: - type: array - items: - type: string - description: Multiple values may be separated by commas. - explode: false - style: form - - in: query - name: filter[resource_tag_value] - schema: - type: string - - in: query - name: filter[resource_tag_value__icontains] - schema: - type: string - - in: query - name: filter[resource_tag_value__in] - schema: - type: array - items: - type: string - description: Multiple values may be separated by commas. - explode: false - style: form - - in: query - name: filter[resource_tags] - schema: - type: array - items: - type: string - description: |- - Filter by resource tags `key:value` pairs. - Multiple values may be separated by commas. - explode: false - style: form - in: query name: filter[resource_type] schema: @@ -6081,13 +5945,10 @@ components: type: array items: type: string - tags: - description: Tags are described as key-value pairs. required: - services - regions - resource_types - - tags FindingMetadataResponse: type: object properties: diff --git a/api/src/backend/api/tests/test_views.py b/api/src/backend/api/tests/test_views.py index 184843af18..0173797062 100644 --- a/api/src/backend/api/tests/test_views.py +++ b/api/src/backend/api/tests/test_views.py @@ -2454,15 +2454,16 @@ class TestFindingViewSet: ("search", "ec2", 2), # full text search on finding tags ("search", "value2", 2), - ("resource_tag_key", "key", 2), - ("resource_tag_key__in", "key,key2", 2), - ("resource_tag_key__icontains", "key", 2), - ("resource_tag_value", "value", 2), - ("resource_tag_value__in", "value,value2", 2), - ("resource_tag_value__icontains", "value", 2), - ("resource_tags", "key:value", 2), - ("resource_tags", "not:exists", 0), - ("resource_tags", "not:exists,key:value", 2), + # Temporary disabled until we implement tag filtering in the UI + # ("resource_tag_key", "key", 2), + # ("resource_tag_key__in", "key,key2", 2), + # ("resource_tag_key__icontains", "key", 2), + # ("resource_tag_value", "value", 2), + # ("resource_tag_value__in", "value,value2", 2), + # ("resource_tag_value__icontains", "value", 2), + # ("resource_tags", "key:value", 2), + # ("resource_tags", "not:exists", 0), + # ("resource_tags", "not:exists,key:value", 2), ] ), ) @@ -2611,7 +2612,8 @@ class TestFindingViewSet: expected_services = {"ec2", "s3"} expected_regions = {"eu-west-1", "us-east-1"} - expected_tags = {"key": ["value"], "key2": ["value2"]} + # Temporarily disabled until we implement tag filtering in the UI + # expected_tags = {"key": ["value"], "key2": ["value2"]} expected_resource_types = {"prowler-test"} assert data["data"]["type"] == "findings-metadata" @@ -2621,7 +2623,7 @@ class TestFindingViewSet: assert ( set(data["data"]["attributes"]["resource_types"]) == expected_resource_types ) - assert data["data"]["attributes"]["tags"] == expected_tags + # assert data["data"]["attributes"]["tags"] == expected_tags def test_findings_metadata_severity_retrieve( self, authenticated_client, findings_fixture @@ -2638,7 +2640,8 @@ class TestFindingViewSet: expected_services = {"s3"} expected_regions = {"eu-west-1"} - expected_tags = {"key": ["value"], "key2": ["value2"]} + # Temporary disabled until we implement tag filtering in the UI + # expected_tags = {"key": ["value"], "key2": ["value2"]} expected_resource_types = {"prowler-test"} assert data["data"]["type"] == "findings-metadata" @@ -2648,7 +2651,7 @@ class TestFindingViewSet: assert ( set(data["data"]["attributes"]["resource_types"]) == expected_resource_types ) - assert data["data"]["attributes"]["tags"] == expected_tags + # assert data["data"]["attributes"]["tags"] == expected_tags def test_findings_metadata_future_date(self, authenticated_client): response = authenticated_client.get( @@ -2660,7 +2663,8 @@ class TestFindingViewSet: assert data["data"]["id"] is None assert data["data"]["attributes"]["services"] == [] assert data["data"]["attributes"]["regions"] == [] - assert data["data"]["attributes"]["tags"] == {} + # Temporary disabled until we implement tag filtering in the UI + # assert data["data"]["attributes"]["tags"] == {} assert data["data"]["attributes"]["resource_types"] == [] def test_findings_metadata_invalid_date(self, authenticated_client): diff --git a/api/src/backend/api/v1/serializers.py b/api/src/backend/api/v1/serializers.py index 6046799d0c..4f17d2d61c 100644 --- a/api/src/backend/api/v1/serializers.py +++ b/api/src/backend/api/v1/serializers.py @@ -933,7 +933,8 @@ class FindingMetadataSerializer(serializers.Serializer): resource_types = serializers.ListField( child=serializers.CharField(), allow_empty=True ) - tags = serializers.JSONField(help_text="Tags are described as key-value pairs.") + # Temporarily disabled until we implement tag filtering in the UI + # tags = serializers.JSONField(help_text="Tags are described as key-value pairs.") class Meta: resource_name = "findings-metadata" diff --git a/api/src/backend/api/v1/urls.py b/api/src/backend/api/v1/urls.py index fd3f9ad18a..6b230960e1 100644 --- a/api/src/backend/api/v1/urls.py +++ b/api/src/backend/api/v1/urls.py @@ -1,30 +1,31 @@ +from django.conf import settings from django.urls import include, path from drf_spectacular.views import SpectacularRedocView from rest_framework_nested import routers from api.v1.views import ( + ComplianceOverviewViewSet, CustomTokenObtainView, CustomTokenRefreshView, FindingViewSet, - MembershipViewSet, - ProviderGroupViewSet, - ProviderGroupProvidersRelationshipView, - ProviderSecretViewSet, - InvitationViewSet, InvitationAcceptViewSet, - RoleViewSet, - RoleProviderGroupRelationshipView, - UserRoleRelationshipView, + InvitationViewSet, + MembershipViewSet, OverviewViewSet, - ComplianceOverviewViewSet, + ProviderGroupProvidersRelationshipView, + ProviderGroupViewSet, + ProviderSecretViewSet, ProviderViewSet, ResourceViewSet, + RoleProviderGroupRelationshipView, + RoleViewSet, ScanViewSet, ScheduleViewSet, SchemaView, TaskViewSet, TenantMembersViewSet, TenantViewSet, + UserRoleRelationshipView, UserViewSet, ) @@ -112,3 +113,6 @@ urlpatterns = [ path("schema", SchemaView.as_view(), name="schema"), path("docs", SpectacularRedocView.as_view(url_name="schema"), name="docs"), ] + +if settings.DEBUG: + urlpatterns += [path("silk/", include("silk.urls", namespace="silk"))] diff --git a/api/src/backend/api/v1/views.py b/api/src/backend/api/v1/views.py index debfcdd57c..3ecb6474fb 100644 --- a/api/src/backend/api/v1/views.py +++ b/api/src/backend/api/v1/views.py @@ -4,7 +4,7 @@ from django.contrib.postgres.aggregates import ArrayAgg from django.contrib.postgres.search import SearchQuery from django.db import transaction from django.db.models import Count, F, OuterRef, Prefetch, Q, Subquery, Sum -from django.db.models.functions import Coalesce, JSONObject +from django.db.models.functions import Coalesce from django.urls import reverse from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_control @@ -193,7 +193,7 @@ class SchemaView(SpectacularAPIView): def get(self, request, *args, **kwargs): spectacular_settings.TITLE = "Prowler API" - spectacular_settings.VERSION = "1.3.1" + spectacular_settings.VERSION = "1.3.2" spectacular_settings.DESCRIPTION = ( "Prowler API specification.\n\nThis file is auto-generated." ) @@ -1392,48 +1392,59 @@ class FindingViewSet(BaseRLSViewSet): @action(detail=False, methods=["get"], url_name="metadata") def metadata(self, request): + tenant_id = self.request.tenant_id queryset = self.get_queryset() filtered_queryset = self.filter_queryset(queryset) - result = filtered_queryset.aggregate( - services=ArrayAgg("resources__service", flat=True, distinct=True), - regions=ArrayAgg("resources__region", flat=True, distinct=True), - tags=ArrayAgg( - JSONObject( - key=F("resources__tags__key"), value=F("resources__tags__value") - ), - distinct=True, - filter=Q(resources__tags__key__isnull=False), - ), - resource_types=ArrayAgg("resources__type", flat=True, distinct=True), - ) - if result["services"] is None: - result["services"] = [] - if result["regions"] is None: - result["regions"] = [] - if result["regions"] is None: - result["regions"] = [] - if result["resource_types"] is None: - result["resource_types"] = [] - if result["tags"] is None: - result["tags"] = [] + relevant_resources = Resource.objects.filter( + tenant_id=tenant_id, findings__in=filtered_queryset + ).distinct() - tags_dict = {} - for t in result["tags"]: - key, value = t["key"], t["value"] - if key not in tags_dict: - tags_dict[key] = [] - tags_dict[key].append(value) - - result["tags"] = tags_dict - - serializer = self.get_serializer( - data=result, + services = ( + relevant_resources.values_list("service", flat=True) + .distinct() + .order_by("service") ) + regions = ( + relevant_resources.exclude(region="") + .values_list("region", flat=True) + .distinct() + .order_by("region") + ) + + resource_types = ( + relevant_resources.values_list("type", flat=True) + .distinct() + .order_by("type") + ) + + # Temporarily disabled until we implement tag filtering in the UI + # tag_data = ( + # relevant_resources + # .filter(tags__key__isnull=False, tags__value__isnull=False) + # .exclude(tags__key="") + # .exclude(tags__value="") + # .values("tags__key", "tags__value") + # .distinct() + # .order_by("tags__key", "tags__value") + # ) + # + # tags_dict = {} + # for row in tag_data: + # k, v = row["tags__key"], row["tags__value"] + # tags_dict.setdefault(k, []).append(v) + + result = { + "services": list(services), + "regions": list(regions), + "resource_types": list(resource_types), + # "tags": tags_dict + } + + serializer = self.get_serializer(data=result) serializer.is_valid(raise_exception=True) - - return Response(data=serializer.data, status=status.HTTP_200_OK) + return Response(serializer.data, status=status.HTTP_200_OK) @extend_schema_view( diff --git a/api/src/backend/config/django/devel.py b/api/src/backend/config/django/devel.py index 825e1ce36a..6ee92a6ecb 100644 --- a/api/src/backend/config/django/devel.py +++ b/api/src/backend/config/django/devel.py @@ -37,3 +37,9 @@ REST_FRAMEWORK["DEFAULT_FILTER_BACKENDS"] = tuple( # noqa: F405 ) + ("api.filters.CustomDjangoFilterBackend",) SECRETS_ENCRYPTION_KEY = "ZMiYVo7m4Fbe2eXXPyrwxdJss2WSalXSv3xHBcJkPl0=" + +MIDDLEWARE += [ # noqa: F405 + "silk.middleware.SilkyMiddleware", +] + +INSTALLED_APPS += ["silk"] # noqa: F405 diff --git a/api/src/backend/tasks/jobs/scan.py b/api/src/backend/tasks/jobs/scan.py index 25993f5bbc..89612ae6fd 100644 --- a/api/src/backend/tasks/jobs/scan.py +++ b/api/src/backend/tasks/jobs/scan.py @@ -152,6 +152,9 @@ def perform_prowler_scan( for progress, findings in prowler_scan.scan(): for finding in findings: + if finding is None: + logger.error(f"None finding detected on scan {scan_id}.") + continue for attempt in range(CELERY_DEADLOCK_ATTEMPTS): try: with rls_transaction(tenant_id): @@ -176,7 +179,10 @@ def perform_prowler_scan( # Update resource fields if necessary updated_fields = [] - if resource_instance.region != finding.region: + if ( + finding.region + and resource_instance.region != finding.region + ): resource_instance.region = finding.region updated_fields.append("region") if resource_instance.service != finding.service_name: From 5bfaedf903bf06e0b3b07a01c8b3d818a84bdb15 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 30 Jan 2025 14:05:39 +0100 Subject: [PATCH 03/31] fix: Enable hot reloading when using Docker Compose for UI (#6750) --- docker-compose-dev.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index d49af9dfbb..c8ecea96d5 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -35,6 +35,9 @@ services: required: false ports: - 3000:3000 + volumes: + - "./ui:/app" + - "/app/node_modules" postgres: image: postgres:16.3-alpine3.20 From 5186e029b3fc89db58517c6f059739ee256dddb0 Mon Sep 17 00:00:00 2001 From: Pepe Fagoaga Date: Thu, 30 Jan 2025 20:48:51 +0545 Subject: [PATCH 04/31] fix(set_report_color): Add more details to error (#6751) --- prowler/lib/outputs/outputs.py | 4 +++- tests/lib/outputs/outputs_test.py | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/prowler/lib/outputs/outputs.py b/prowler/lib/outputs/outputs.py index 2a61e17c36..b4699157d7 100644 --- a/prowler/lib/outputs/outputs.py +++ b/prowler/lib/outputs/outputs.py @@ -83,7 +83,9 @@ def set_report_color(status: str, muted: bool = False) -> str: elif status == "MANUAL": color = Fore.YELLOW else: - raise Exception("Invalid Report Status. Must be PASS, FAIL or MANUAL.") + raise Exception( + f"Invalid Report Status: {status}. Must be PASS, FAIL or MANUAL." + ) return color diff --git a/tests/lib/outputs/outputs_test.py b/tests/lib/outputs/outputs_test.py index 624366af5e..dbf7d5c8bd 100644 --- a/tests/lib/outputs/outputs_test.py +++ b/tests/lib/outputs/outputs_test.py @@ -21,7 +21,6 @@ from prowler.lib.outputs.utils import ( class TestOutputs: - def test_set_report_color(self): test_status = ["PASS", "FAIL", "MANUAL"] test_colors = [Fore.GREEN, Fore.RED, Fore.YELLOW] @@ -35,8 +34,9 @@ class TestOutputs: with pytest.raises(Exception) as exc: set_report_color(test_status) - assert "Invalid Report Status. Must be PASS, FAIL or MANUAL" in str(exc.value) - assert exc.type == Exception + assert "Invalid Report Status: INVALID. Must be PASS, FAIL or MANUAL" in str( + exc.value + ) def test_unroll_list_no_separator(self): list = ["test", "test1", "test2"] From 712ba84f068e7f158658e1129363cd46ab749f98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20Poyatos?= Date: Thu, 30 Jan 2025 16:06:12 +0100 Subject: [PATCH 05/31] feat(scans): Optimize read queries during scans (#6753) --- api/src/backend/tasks/jobs/scan.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/api/src/backend/tasks/jobs/scan.py b/api/src/backend/tasks/jobs/scan.py index 89612ae6fd..b11366a859 100644 --- a/api/src/backend/tasks/jobs/scan.py +++ b/api/src/backend/tasks/jobs/scan.py @@ -228,8 +228,10 @@ def perform_prowler_scan( last_first_seen_at = None if finding_uid not in last_status_cache: most_recent_finding = ( - Finding.objects.filter(uid=finding_uid) - .order_by("-id") + Finding.all_objects.filter( + tenant_id=tenant_id, uid=finding_uid + ) + .order_by("-inserted_at") .values("status", "first_seen_at") .first() ) @@ -378,7 +380,7 @@ def aggregate_findings(tenant_id: str, scan_id: str): - muted_changed: Muted findings with a delta of 'changed'. """ with rls_transaction(tenant_id): - findings = Finding.objects.filter(scan_id=scan_id) + findings = Finding.objects.filter(tenant_id=tenant_id, scan_id=scan_id) aggregation = findings.values( "check_id", From 627c11503f1484db1728184bf19bf0dd458d4cdd Mon Sep 17 00:00:00 2001 From: Pepe Fagoaga Date: Thu, 30 Jan 2025 21:46:43 +0545 Subject: [PATCH 06/31] fix(db_event): Handle other events (#6754) --- ...ds_instance_critical_event_subscription.py | 3 ++ ...stance_critical_event_subscription_test.py | 49 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/prowler/providers/aws/services/rds/rds_instance_critical_event_subscription/rds_instance_critical_event_subscription.py b/prowler/providers/aws/services/rds/rds_instance_critical_event_subscription/rds_instance_critical_event_subscription.py index e2f379232b..43751d7369 100644 --- a/prowler/providers/aws/services/rds/rds_instance_critical_event_subscription/rds_instance_critical_event_subscription.py +++ b/prowler/providers/aws/services/rds/rds_instance_critical_event_subscription/rds_instance_critical_event_subscription.py @@ -54,6 +54,9 @@ class rds_instance_critical_event_subscription(Check): }: report.status = "FAIL" report.status_extended = "RDS instance event category of maintenance is not subscribed." + else: + report.status = "FAIL" + report.status_extended = "RDS instance event categories of maintenance, configuration change, and failure are not subscribed." findings.append(report) return findings diff --git a/tests/providers/aws/services/rds/rds_instance_critical_event_subscription/rds_instance_critical_event_subscription_test.py b/tests/providers/aws/services/rds/rds_instance_critical_event_subscription/rds_instance_critical_event_subscription_test.py index d72df2303a..02729be231 100644 --- a/tests/providers/aws/services/rds/rds_instance_critical_event_subscription/rds_instance_critical_event_subscription_test.py +++ b/tests/providers/aws/services/rds/rds_instance_critical_event_subscription/rds_instance_critical_event_subscription_test.py @@ -483,3 +483,52 @@ class Test_rds_instance_critical_event_subscription: == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:es:TestSub" ) assert result[0].resource_tags == [] + + @mock_aws + def test_rds_instance_event_invalid(self): + conn = client("rds", region_name=AWS_REGION_US_EAST_1) + conn.create_db_parameter_group( + DBParameterGroupName="test", + DBParameterGroupFamily="default.aurora-postgresql14", + Description="test parameter group", + ) + conn.create_event_subscription( + SubscriptionName="TestSub", + SnsTopicArn=f"arn:aws:sns:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:test", + SourceType="db-instance", + EventCategories=["invalid"], + Enabled=True, + ) + from prowler.providers.aws.services.rds.rds_service import RDS + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.rds.rds_instance_critical_event_subscription.rds_instance_critical_event_subscription.rds_client", + new=RDS(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.rds.rds_instance_critical_event_subscription.rds_instance_critical_event_subscription import ( + rds_instance_critical_event_subscription, + ) + + check = rds_instance_critical_event_subscription() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "RDS instance event categories of maintenance, configuration change, and failure are not subscribed." + ) + assert result[0].resource_id == "TestSub" + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:es:TestSub" + ) + assert result[0].resource_tags == [] From 18b7b48a991ca0941bd8c41f4f8963ae2e49db3d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Jan 2025 10:07:17 +0100 Subject: [PATCH 07/31] chore(deps): bump microsoft-kiota-abstractions from 1.6.8 to 1.9.1 (#6734) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 188 ++++++++++--------------------------------------- pyproject.toml | 2 +- 2 files changed, 37 insertions(+), 153 deletions(-) diff --git a/poetry.lock b/poetry.lock index b0e6e0e4ad..cbb2504f70 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "about-time" @@ -2359,13 +2359,13 @@ files = [ [[package]] name = "microsoft-kiota-abstractions" -version = "1.6.8" +version = "1.9.1" description = "Core abstractions for kiota generated libraries in Python" optional = false -python-versions = "<4.0,>=3.8" +python-versions = "<4.0,>=3.9" files = [ - {file = "microsoft_kiota_abstractions-1.6.8-py3-none-any.whl", hash = "sha256:12819dee24d5aaa31e99683d938f65e50cbc446de087df244cd26c3326ec4e15"}, - {file = "microsoft_kiota_abstractions-1.6.8.tar.gz", hash = "sha256:7070affabfa7182841646a0c8491cbb240af366aff2b9132f0caa45c4837dd78"}, + {file = "microsoft_kiota_abstractions-1.9.1-py3-none-any.whl", hash = "sha256:aca6147946fc56167038faddda0a07026b0ec4e76666520586de7ac5c1233295"}, + {file = "microsoft_kiota_abstractions-1.9.1.tar.gz", hash = "sha256:291f4f88bec5452b2c6bafed4927640d2c7d2f41b08c941ee786f6ecad3dcaa5"}, ] [package.dependencies] @@ -2375,19 +2375,19 @@ std-uritemplate = ">=2.0.0" [[package]] name = "microsoft-kiota-authentication-azure" -version = "1.6.8" +version = "1.9.1" description = "Core abstractions for kiota generated libraries in Python" optional = false -python-versions = "<4.0,>=3.8" +python-versions = "<4.0,>=3.9" files = [ - {file = "microsoft_kiota_authentication_azure-1.6.8-py3-none-any.whl", hash = "sha256:50455789b7133e27fbccec839d93e40d2637d18593a93921ae1338880c5b5b3b"}, - {file = "microsoft_kiota_authentication_azure-1.6.8.tar.gz", hash = "sha256:fef23f43cd4d3b9ef839c8b3d1f675ec4a1120c150f963d8c4551c5e19ac3b36"}, + {file = "microsoft_kiota_authentication_azure-1.9.1-py3-none-any.whl", hash = "sha256:3a3030b01e0cbf007736ec6548ac483742e04ad0d20979b49e293a683f839fc6"}, + {file = "microsoft_kiota_authentication_azure-1.9.1.tar.gz", hash = "sha256:8ba31b1ecf78777128daf3191c7ecd28bfc2d10d0fe80260dd5c07a9ccd5d7f5"}, ] [package.dependencies] aiohttp = ">=3.8.0" azure-core = ">=1.21.1" -microsoft-kiota-abstractions = ">=1.6.8,<1.7.0" +microsoft-kiota-abstractions = ">=1.9.1,<1.10.0" opentelemetry-api = ">=1.27.0" opentelemetry-sdk = ">=1.27.0" @@ -2408,83 +2408,61 @@ microsoft-kiota_abstractions = ">=1.0.0,<2.0.0" opentelemetry-api = ">=1.20.0" opentelemetry-sdk = ">=1.20.0" -[[package]] -name = "microsoft-kiota-http" -version = "1.6.8" -description = "Core abstractions for kiota generated libraries in Python" -optional = false -python-versions = "<4.0,>=3.8" -files = [ - {file = "microsoft_kiota_http-1.6.8-py3-none-any.whl", hash = "sha256:7ff76a308351d885453185d6a6538c47a64ebdc7661cce46a904e89e2ceb9a1d"}, - {file = "microsoft_kiota_http-1.6.8.tar.gz", hash = "sha256:67242690b79a30c0cadf823675249269e4bc020283e3d65b33af7d771df64df8"}, -] - -[package.dependencies] -httpx = {version = ">=0.28", extras = ["http2"]} -microsoft-kiota-abstractions = ">=1.6.8,<1.7.0" -opentelemetry-api = ">=1.27.0" -opentelemetry-sdk = ">=1.27.0" -urllib3 = ">=2.2.2,<3.0.0" - [[package]] name = "microsoft-kiota-serialization-form" -version = "1.6.8" +version = "1.9.1" description = "Core abstractions for kiota generated libraries in Python" optional = false -python-versions = "<4.0,>=3.8" +python-versions = "<4.0,>=3.9" files = [ - {file = "microsoft_kiota_serialization_form-1.6.8-py3-none-any.whl", hash = "sha256:ca7dd19e173aa87c68b38c5056cc0921570c8c86f3ba5511d1616cf97e2f0f67"}, - {file = "microsoft_kiota_serialization_form-1.6.8.tar.gz", hash = "sha256:bb9eb98b3abf596b4bfe208014dff948361ff48a757316ac58e19c31ab8d640a"}, + {file = "microsoft_kiota_serialization_form-1.9.1-py3-none-any.whl", hash = "sha256:d86c0dc08b51288f2851a265dde729b200998bca1dd1681bbebcb27cd274ba34"}, + {file = "microsoft_kiota_serialization_form-1.9.1.tar.gz", hash = "sha256:58b81eca5e0ad66bcbd6a4b65ba91e6255d3510f4c4f576fb4f5e83ca9a310c3"}, ] [package.dependencies] -microsoft-kiota-abstractions = ">=1.6.8,<1.7.0" -pendulum = ">=3.0.0b1" +microsoft-kiota-abstractions = ">=1.9.1,<1.10.0" [[package]] name = "microsoft-kiota-serialization-json" -version = "1.6.8" +version = "1.9.1" description = "Core abstractions for kiota generated libraries in Python" optional = false -python-versions = "<4.0,>=3.8" +python-versions = "<4.0,>=3.9" files = [ - {file = "microsoft_kiota_serialization_json-1.6.8-py3-none-any.whl", hash = "sha256:2734c2ad64cc089441279e4962f6fedf41af040730f6eab1533890cd5377aff5"}, - {file = "microsoft_kiota_serialization_json-1.6.8.tar.gz", hash = "sha256:89e2dd0eb4eaaa6ab74fa89ab5d84c5a53464e73b85eb7085f0aa4560a2b8183"}, + {file = "microsoft_kiota_serialization_json-1.9.1-py3-none-any.whl", hash = "sha256:ab752bf642a77266713bac3942a4c547dde60b917f674a9ab63261490fecf841"}, + {file = "microsoft_kiota_serialization_json-1.9.1.tar.gz", hash = "sha256:0039458b885875daf246f1e8c0296551695e0ec70f3e4689b00b270ce923c0cc"}, ] [package.dependencies] -microsoft-kiota-abstractions = ">=1.6.8,<1.7.0" -pendulum = ">=3.0.0b1" +microsoft-kiota-abstractions = ">=1.9.1,<1.10.0" [[package]] name = "microsoft-kiota-serialization-multipart" -version = "1.6.8" +version = "1.9.1" description = "Core abstractions for kiota generated libraries in Python" optional = false -python-versions = "<4.0,>=3.8" +python-versions = "<4.0,>=3.9" files = [ - {file = "microsoft_kiota_serialization_multipart-1.6.8-py3-none-any.whl", hash = "sha256:1ecdd15dd1f78aed031d7d1828b6fbc00c633542d863c23f96fdd0a61bfb189a"}, - {file = "microsoft_kiota_serialization_multipart-1.6.8.tar.gz", hash = "sha256:3d95c6d7186588af7a1d3aa852ce42077f80487b8b3c60e36fe109a8b4918c03"}, + {file = "microsoft_kiota_serialization_multipart-1.9.1-py3-none-any.whl", hash = "sha256:1ee69d8e3b2c0d24431b85fc1628534f97dcafed45dcb6bb3f0143824ce8a665"}, + {file = "microsoft_kiota_serialization_multipart-1.9.1.tar.gz", hash = "sha256:288790a486aad33aac0a7329f05b8851e9be82f50cc9a8f19fe6f267a4f2b183"}, ] [package.dependencies] -microsoft-kiota-abstractions = ">=1.6.8,<1.7.0" -pendulum = ">=3.0.0b1" +microsoft-kiota-abstractions = ">=1.9.1,<1.10.0" [[package]] name = "microsoft-kiota-serialization-text" -version = "1.6.8" +version = "1.9.1" description = "Core abstractions for kiota generated libraries in Python" optional = false -python-versions = "<4.0,>=3.8" +python-versions = "<4.0,>=3.9" files = [ - {file = "microsoft_kiota_serialization_text-1.6.8-py3-none-any.whl", hash = "sha256:4e5e287a614d362f864b5061dca0861c3f70b8792ec72967d1bff23944da1e80"}, - {file = "microsoft_kiota_serialization_text-1.6.8.tar.gz", hash = "sha256:687d4858337eaf4f351b12ed1c6c934d869560f54ee3855bfdde589660e07208"}, + {file = "microsoft_kiota_serialization_text-1.9.1-py3-none-any.whl", hash = "sha256:021fbfad887f7525f9e1c8bbe225d0aa1b25befe54357ae0f739f47576d946ff"}, + {file = "microsoft_kiota_serialization_text-1.9.1.tar.gz", hash = "sha256:b4b1f61c2388edd704ab33a1792f92f3507db9648d389197a625e152b52743ab"}, ] [package.dependencies] -microsoft-kiota-abstractions = ">=1.6.8,<1.7.0" -python-dateutil = "2.9.0.post0" +microsoft-kiota-abstractions = ">=1.9.1,<1.10.0" [[package]] name = "mkdocs" @@ -3241,105 +3219,6 @@ files = [ {file = "pbr-6.1.0.tar.gz", hash = "sha256:788183e382e3d1d7707db08978239965e8b9e4e5ed42669bf4758186734d5f24"}, ] -[[package]] -name = "pendulum" -version = "3.0.0" -description = "Python datetimes made easy" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pendulum-3.0.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2cf9e53ef11668e07f73190c805dbdf07a1939c3298b78d5a9203a86775d1bfd"}, - {file = "pendulum-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fb551b9b5e6059377889d2d878d940fd0bbb80ae4810543db18e6f77b02c5ef6"}, - {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c58227ac260d5b01fc1025176d7b31858c9f62595737f350d22124a9a3ad82d"}, - {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60fb6f415fea93a11c52578eaa10594568a6716602be8430b167eb0d730f3332"}, - {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b69f6b4dbcb86f2c2fe696ba991e67347bcf87fe601362a1aba6431454b46bde"}, - {file = "pendulum-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:138afa9c373ee450ede206db5a5e9004fd3011b3c6bbe1e57015395cd076a09f"}, - {file = "pendulum-3.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:83d9031f39c6da9677164241fd0d37fbfc9dc8ade7043b5d6d62f56e81af8ad2"}, - {file = "pendulum-3.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0c2308af4033fa534f089595bcd40a95a39988ce4059ccd3dc6acb9ef14ca44a"}, - {file = "pendulum-3.0.0-cp310-none-win_amd64.whl", hash = "sha256:9a59637cdb8462bdf2dbcb9d389518c0263799189d773ad5c11db6b13064fa79"}, - {file = "pendulum-3.0.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3725245c0352c95d6ca297193192020d1b0c0f83d5ee6bb09964edc2b5a2d508"}, - {file = "pendulum-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6c035f03a3e565ed132927e2c1b691de0dbf4eb53b02a5a3c5a97e1a64e17bec"}, - {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597e66e63cbd68dd6d58ac46cb7a92363d2088d37ccde2dae4332ef23e95cd00"}, - {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99a0f8172e19f3f0c0e4ace0ad1595134d5243cf75985dc2233e8f9e8de263ca"}, - {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:77d8839e20f54706aed425bec82a83b4aec74db07f26acd039905d1237a5e1d4"}, - {file = "pendulum-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afde30e8146292b059020fbc8b6f8fd4a60ae7c5e6f0afef937bbb24880bdf01"}, - {file = "pendulum-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:660434a6fcf6303c4efd36713ca9212c753140107ee169a3fc6c49c4711c2a05"}, - {file = "pendulum-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dee9e5a48c6999dc1106eb7eea3e3a50e98a50651b72c08a87ee2154e544b33e"}, - {file = "pendulum-3.0.0-cp311-none-win_amd64.whl", hash = "sha256:d4cdecde90aec2d67cebe4042fd2a87a4441cc02152ed7ed8fb3ebb110b94ec4"}, - {file = "pendulum-3.0.0-cp311-none-win_arm64.whl", hash = "sha256:773c3bc4ddda2dda9f1b9d51fe06762f9200f3293d75c4660c19b2614b991d83"}, - {file = "pendulum-3.0.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:409e64e41418c49f973d43a28afe5df1df4f1dd87c41c7c90f1a63f61ae0f1f7"}, - {file = "pendulum-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a38ad2121c5ec7c4c190c7334e789c3b4624798859156b138fcc4d92295835dc"}, - {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fde4d0b2024b9785f66b7f30ed59281bd60d63d9213cda0eb0910ead777f6d37"}, - {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b2c5675769fb6d4c11238132962939b960fcb365436b6d623c5864287faa319"}, - {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8af95e03e066826f0f4c65811cbee1b3123d4a45a1c3a2b4fc23c4b0dff893b5"}, - {file = "pendulum-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2165a8f33cb15e06c67070b8afc87a62b85c5a273e3aaa6bc9d15c93a4920d6f"}, - {file = "pendulum-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ad5e65b874b5e56bd942546ea7ba9dd1d6a25121db1c517700f1c9de91b28518"}, - {file = "pendulum-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17fe4b2c844bbf5f0ece69cfd959fa02957c61317b2161763950d88fed8e13b9"}, - {file = "pendulum-3.0.0-cp312-none-win_amd64.whl", hash = "sha256:78f8f4e7efe5066aca24a7a57511b9c2119f5c2b5eb81c46ff9222ce11e0a7a5"}, - {file = "pendulum-3.0.0-cp312-none-win_arm64.whl", hash = "sha256:28f49d8d1e32aae9c284a90b6bb3873eee15ec6e1d9042edd611b22a94ac462f"}, - {file = "pendulum-3.0.0-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:d4e2512f4e1a4670284a153b214db9719eb5d14ac55ada5b76cbdb8c5c00399d"}, - {file = "pendulum-3.0.0-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:3d897eb50883cc58d9b92f6405245f84b9286cd2de6e8694cb9ea5cb15195a32"}, - {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e169cc2ca419517f397811bbe4589cf3cd13fca6dc38bb352ba15ea90739ebb"}, - {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f17c3084a4524ebefd9255513692f7e7360e23c8853dc6f10c64cc184e1217ab"}, - {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:826d6e258052715f64d05ae0fc9040c0151e6a87aae7c109ba9a0ed930ce4000"}, - {file = "pendulum-3.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2aae97087872ef152a0c40e06100b3665d8cb86b59bc8471ca7c26132fccd0f"}, - {file = "pendulum-3.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ac65eeec2250d03106b5e81284ad47f0d417ca299a45e89ccc69e36130ca8bc7"}, - {file = "pendulum-3.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a5346d08f3f4a6e9e672187faa179c7bf9227897081d7121866358af369f44f9"}, - {file = "pendulum-3.0.0-cp37-none-win_amd64.whl", hash = "sha256:235d64e87946d8f95c796af34818c76e0f88c94d624c268693c85b723b698aa9"}, - {file = "pendulum-3.0.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:6a881d9c2a7f85bc9adafcfe671df5207f51f5715ae61f5d838b77a1356e8b7b"}, - {file = "pendulum-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d7762d2076b9b1cb718a6631ad6c16c23fc3fac76cbb8c454e81e80be98daa34"}, - {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e8e36a8130819d97a479a0e7bf379b66b3b1b520e5dc46bd7eb14634338df8c"}, - {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7dc843253ac373358ffc0711960e2dd5b94ab67530a3e204d85c6e8cb2c5fa10"}, - {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a78ad3635d609ceb1e97d6aedef6a6a6f93433ddb2312888e668365908c7120"}, - {file = "pendulum-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b30a137e9e0d1f751e60e67d11fc67781a572db76b2296f7b4d44554761049d6"}, - {file = "pendulum-3.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c95984037987f4a457bb760455d9ca80467be792236b69d0084f228a8ada0162"}, - {file = "pendulum-3.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d29c6e578fe0f893766c0d286adbf0b3c726a4e2341eba0917ec79c50274ec16"}, - {file = "pendulum-3.0.0-cp38-none-win_amd64.whl", hash = "sha256:deaba8e16dbfcb3d7a6b5fabdd5a38b7c982809567479987b9c89572df62e027"}, - {file = "pendulum-3.0.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b11aceea5b20b4b5382962b321dbc354af0defe35daa84e9ff3aae3c230df694"}, - {file = "pendulum-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a90d4d504e82ad236afac9adca4d6a19e4865f717034fc69bafb112c320dcc8f"}, - {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:825799c6b66e3734227756fa746cc34b3549c48693325b8b9f823cb7d21b19ac"}, - {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad769e98dc07972e24afe0cff8d365cb6f0ebc7e65620aa1976fcfbcadc4c6f3"}, - {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6fc26907eb5fb8cc6188cc620bc2075a6c534d981a2f045daa5f79dfe50d512"}, - {file = "pendulum-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c717eab1b6d898c00a3e0fa7781d615b5c5136bbd40abe82be100bb06df7a56"}, - {file = "pendulum-3.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3ddd1d66d1a714ce43acfe337190be055cdc221d911fc886d5a3aae28e14b76d"}, - {file = "pendulum-3.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:822172853d7a9cf6da95d7b66a16c7160cb99ae6df55d44373888181d7a06edc"}, - {file = "pendulum-3.0.0-cp39-none-win_amd64.whl", hash = "sha256:840de1b49cf1ec54c225a2a6f4f0784d50bd47f68e41dc005b7f67c7d5b5f3ae"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3b1f74d1e6ffe5d01d6023870e2ce5c2191486928823196f8575dcc786e107b1"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:729e9f93756a2cdfa77d0fc82068346e9731c7e884097160603872686e570f07"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e586acc0b450cd21cbf0db6bae386237011b75260a3adceddc4be15334689a9a"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22e7944ffc1f0099a79ff468ee9630c73f8c7835cd76fdb57ef7320e6a409df4"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fa30af36bd8e50686846bdace37cf6707bdd044e5cb6e1109acbad3277232e04"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:440215347b11914ae707981b9a57ab9c7b6983ab0babde07063c6ee75c0dc6e7"}, - {file = "pendulum-3.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:314c4038dc5e6a52991570f50edb2f08c339debdf8cea68ac355b32c4174e820"}, - {file = "pendulum-3.0.0-pp37-pypy37_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5acb1d386337415f74f4d1955c4ce8d0201978c162927d07df8eb0692b2d8533"}, - {file = "pendulum-3.0.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a789e12fbdefaffb7b8ac67f9d8f22ba17a3050ceaaa635cd1cc4645773a4b1e"}, - {file = "pendulum-3.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:860aa9b8a888e5913bd70d819306749e5eb488e6b99cd6c47beb701b22bdecf5"}, - {file = "pendulum-3.0.0-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5ebc65ea033ef0281368217fbf59f5cb05b338ac4dd23d60959c7afcd79a60a0"}, - {file = "pendulum-3.0.0-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d9fef18ab0386ef6a9ac7bad7e43ded42c83ff7ad412f950633854f90d59afa8"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1c134ba2f0571d0b68b83f6972e2307a55a5a849e7dac8505c715c531d2a8795"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:385680812e7e18af200bb9b4a49777418c32422d05ad5a8eb85144c4a285907b"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eec91cd87c59fb32ec49eb722f375bd58f4be790cae11c1b70fac3ee4f00da0"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4386bffeca23c4b69ad50a36211f75b35a4deb6210bdca112ac3043deb7e494a"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:dfbcf1661d7146d7698da4b86e7f04814221081e9fe154183e34f4c5f5fa3bf8"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:04a1094a5aa1daa34a6b57c865b25f691848c61583fb22722a4df5699f6bf74c"}, - {file = "pendulum-3.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5b0ec85b9045bd49dd3a3493a5e7ddfd31c36a2a60da387c419fa04abcaecb23"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0a15b90129765b705eb2039062a6daf4d22c4e28d1a54fa260892e8c3ae6e157"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:bb8f6d7acd67a67d6fedd361ad2958ff0539445ef51cbe8cd288db4306503cd0"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd69b15374bef7e4b4440612915315cc42e8575fcda2a3d7586a0d88192d0c88"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc00f8110db6898360c53c812872662e077eaf9c75515d53ecc65d886eec209a"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:83a44e8b40655d0ba565a5c3d1365d27e3e6778ae2a05b69124db9e471255c4a"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:1a3604e9fbc06b788041b2a8b78f75c243021e0f512447806a6d37ee5214905d"}, - {file = "pendulum-3.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:92c307ae7accebd06cbae4729f0ba9fa724df5f7d91a0964b1b972a22baa482b"}, - {file = "pendulum-3.0.0.tar.gz", hash = "sha256:5d034998dea404ec31fae27af6b22cff1708f830a1ed7353be4d1019bb9f584e"}, -] - -[package.dependencies] -python-dateutil = ">=2.6" -tzdata = ">=2020.1" - -[package.extras] -test = ["time-machine (>=2.6.0)"] - [[package]] name = "platformdirs" version = "4.3.6" @@ -4431,6 +4310,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"}, @@ -4439,6 +4319,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"}, @@ -4447,6 +4328,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"}, @@ -4455,6 +4337,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"}, @@ -4463,6 +4346,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"}, {file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"}, @@ -5206,4 +5090,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.13" -content-hash = "f2d3920001616ff9b66353b61d20dadc358787aa154c31fcbd8f4baf45c36498" +content-hash = "19e7c4654f280256e53757abd04cb63938d34ae4d8305a77f21e75c891b5376d" diff --git a/pyproject.toml b/pyproject.toml index ff3156c743..9cb926c96e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ google-api-python-client = "2.159.0" google-auth-httplib2 = ">=0.1,<0.3" jsonschema = "4.23.0" kubernetes = "31.0.0" -microsoft-kiota-abstractions = "1.6.8" +microsoft-kiota-abstractions = "1.9.1" msgraph-sdk = "1.18.0" numpy = "2.0.2" pandas = "2.2.3" From 1256c040e9d4bcb02633b86f1523608e3f0804a3 Mon Sep 17 00:00:00 2001 From: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Date: Fri, 31 Jan 2025 12:32:39 +0100 Subject: [PATCH 08/31] fix: microsoft365 mutelist (#6724) --- prowler/lib/check/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/prowler/lib/check/models.py b/prowler/lib/check/models.py index 2aca0e83c1..928d68f075 100644 --- a/prowler/lib/check/models.py +++ b/prowler/lib/check/models.py @@ -541,6 +541,7 @@ class Check_Report_Microsoft365(Check_Report): resource_name: str resource_id: str + tenant_id: str location: str def __init__(self, metadata: Dict, resource: Any) -> None: @@ -555,6 +556,7 @@ class Check_Report_Microsoft365(Check_Report): resource, "name", getattr(resource, "resource_name", "") ) self.resource_id = getattr(resource, "id", getattr(resource, "resource_id", "")) + self.tenant_id = getattr(resource, "tenant_id", "") self.location = getattr(resource, "location", "global") From 763130f253cb119d30b413f0b917abfbe427193b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20Poyatos?= Date: Fri, 31 Jan 2025 14:45:08 +0100 Subject: [PATCH 09/31] fix(celery): Kill celery worker process after every task to release memory (#6761) --- api/docker-entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/docker-entrypoint.sh b/api/docker-entrypoint.sh index 62ceb69a0f..893d94af21 100755 --- a/api/docker-entrypoint.sh +++ b/api/docker-entrypoint.sh @@ -28,7 +28,7 @@ start_prod_server() { start_worker() { echo "Starting the worker..." - poetry run python -m celery -A config.celery worker -l "${DJANGO_LOGGING_LEVEL:-info}" -Q celery,scans -E + poetry run python -m celery -A config.celery worker -l "${DJANGO_LOGGING_LEVEL:-info}" -Q celery,scans -E --max-tasks-per-child 1 } start_worker_beat() { From ef6af8e84de0e68285a5b2a297ea02939a82cea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20Poyatos?= Date: Mon, 3 Feb 2025 11:08:27 +0100 Subject: [PATCH 10/31] feat(schedules): Rework daily schedule to always show the next scan (#6700) --- api/pyproject.toml | 2 +- .../0008_daily_scheduled_tasks_update.py | 64 ++++++++++++++++ api/src/backend/api/models.py | 4 + api/src/backend/api/specs/v1.yaml | 2 +- api/src/backend/api/v1/urls.py | 4 - api/src/backend/api/v1/views.py | 2 +- api/src/backend/config/django/devel.py | 6 -- api/src/backend/tasks/beat.py | 55 +++++++++----- api/src/backend/tasks/jobs/scan.py | 7 +- api/src/backend/tasks/tasks.py | 49 +++++++----- api/src/backend/tasks/tests/test_beat.py | 4 + api/src/backend/tasks/tests/test_utils.py | 76 +++++++++++++++++++ api/src/backend/tasks/utils.py | 26 +++++++ 13 files changed, 249 insertions(+), 52 deletions(-) create mode 100644 api/src/backend/api/migrations/0008_daily_scheduled_tasks_update.py create mode 100644 api/src/backend/tasks/tests/test_utils.py create mode 100644 api/src/backend/tasks/utils.py diff --git a/api/pyproject.toml b/api/pyproject.toml index 3c7338eb53..2cccb9ddb3 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -8,7 +8,7 @@ description = "Prowler's API (Django/DRF)" license = "Apache-2.0" name = "prowler-api" package-mode = false -version = "1.3.2" +version = "1.4.0" [tool.poetry.dependencies] celery = {extras = ["pytest"], version = "^5.4.0"} diff --git a/api/src/backend/api/migrations/0008_daily_scheduled_tasks_update.py b/api/src/backend/api/migrations/0008_daily_scheduled_tasks_update.py new file mode 100644 index 0000000000..7f059ea2b8 --- /dev/null +++ b/api/src/backend/api/migrations/0008_daily_scheduled_tasks_update.py @@ -0,0 +1,64 @@ +import json +from datetime import datetime, timedelta, timezone + +import django.db.models.deletion +from django.db import migrations, models +from django_celery_beat.models import PeriodicTask + +from api.db_utils import rls_transaction +from api.models import Scan, StateChoices + + +def migrate_daily_scheduled_scan_tasks(apps, schema_editor): + for daily_scheduled_scan_task in PeriodicTask.objects.filter( + task="scan-perform-scheduled" + ): + task_kwargs = json.loads(daily_scheduled_scan_task.kwargs) + tenant_id = task_kwargs["tenant_id"] + provider_id = task_kwargs["provider_id"] + + current_time = datetime.now(timezone.utc) + scheduled_time_today = datetime.combine( + current_time.date(), + daily_scheduled_scan_task.start_time.time(), + tzinfo=timezone.utc, + ) + + if current_time < scheduled_time_today: + next_scan_date = scheduled_time_today + else: + next_scan_date = scheduled_time_today + timedelta(days=1) + + with rls_transaction(tenant_id): + Scan.objects.create( + tenant_id=tenant_id, + name="Daily scheduled scan", + provider_id=provider_id, + trigger=Scan.TriggerChoices.SCHEDULED, + state=StateChoices.SCHEDULED, + scheduled_at=next_scan_date, + scheduler_task_id=daily_scheduled_scan_task.id, + ) + + +class Migration(migrations.Migration): + atomic = False + + dependencies = [ + ("api", "0007_scan_and_scan_summaries_indexes"), + ("django_celery_beat", "0019_alter_periodictasks_options"), + ] + + operations = [ + migrations.AddField( + model_name="scan", + name="scheduler_task", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="django_celery_beat.periodictask", + ), + ), + migrations.RunPython(migrate_daily_scheduled_scan_tasks), + ] diff --git a/api/src/backend/api/models.py b/api/src/backend/api/models.py index 09014321cb..ef982be8c7 100644 --- a/api/src/backend/api/models.py +++ b/api/src/backend/api/models.py @@ -11,6 +11,7 @@ from django.core.validators import MinLengthValidator from django.db import models from django.db.models import Q from django.utils.translation import gettext_lazy as _ +from django_celery_beat.models import PeriodicTask from django_celery_results.models import TaskResult from psqlextra.manager import PostgresManager from psqlextra.models import PostgresPartitionedModel @@ -410,6 +411,9 @@ class Scan(RowLevelSecurityProtectedModel): started_at = models.DateTimeField(null=True, blank=True) completed_at = models.DateTimeField(null=True, blank=True) next_scan_at = models.DateTimeField(null=True, blank=True) + scheduler_task = models.ForeignKey( + PeriodicTask, on_delete=models.CASCADE, null=True, blank=True + ) # TODO: mutelist foreign key class Meta(RowLevelSecurityProtectedModel.Meta): diff --git a/api/src/backend/api/specs/v1.yaml b/api/src/backend/api/specs/v1.yaml index 7448843b54..ab25ef0e27 100644 --- a/api/src/backend/api/specs/v1.yaml +++ b/api/src/backend/api/specs/v1.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: Prowler API - version: 1.3.2 + version: 1.4.0 description: |- Prowler API specification. diff --git a/api/src/backend/api/v1/urls.py b/api/src/backend/api/v1/urls.py index 6b230960e1..4ff587d913 100644 --- a/api/src/backend/api/v1/urls.py +++ b/api/src/backend/api/v1/urls.py @@ -1,4 +1,3 @@ -from django.conf import settings from django.urls import include, path from drf_spectacular.views import SpectacularRedocView from rest_framework_nested import routers @@ -113,6 +112,3 @@ urlpatterns = [ path("schema", SchemaView.as_view(), name="schema"), path("docs", SpectacularRedocView.as_view(url_name="schema"), name="docs"), ] - -if settings.DEBUG: - urlpatterns += [path("silk/", include("silk.urls", namespace="silk"))] diff --git a/api/src/backend/api/v1/views.py b/api/src/backend/api/v1/views.py index 3ecb6474fb..b11626c829 100644 --- a/api/src/backend/api/v1/views.py +++ b/api/src/backend/api/v1/views.py @@ -193,7 +193,7 @@ class SchemaView(SpectacularAPIView): def get(self, request, *args, **kwargs): spectacular_settings.TITLE = "Prowler API" - spectacular_settings.VERSION = "1.3.2" + spectacular_settings.VERSION = "1.4.0" spectacular_settings.DESCRIPTION = ( "Prowler API specification.\n\nThis file is auto-generated." ) diff --git a/api/src/backend/config/django/devel.py b/api/src/backend/config/django/devel.py index 6ee92a6ecb..825e1ce36a 100644 --- a/api/src/backend/config/django/devel.py +++ b/api/src/backend/config/django/devel.py @@ -37,9 +37,3 @@ REST_FRAMEWORK["DEFAULT_FILTER_BACKENDS"] = tuple( # noqa: F405 ) + ("api.filters.CustomDjangoFilterBackend",) SECRETS_ENCRYPTION_KEY = "ZMiYVo7m4Fbe2eXXPyrwxdJss2WSalXSv3xHBcJkPl0=" - -MIDDLEWARE += [ # noqa: F405 - "silk.middleware.SilkyMiddleware", -] - -INSTALLED_APPS += ["silk"] # noqa: F405 diff --git a/api/src/backend/tasks/beat.py b/api/src/backend/tasks/beat.py index 81dc04abba..6cd8d7a9ce 100644 --- a/api/src/backend/tasks/beat.py +++ b/api/src/backend/tasks/beat.py @@ -5,10 +5,14 @@ from django_celery_beat.models import IntervalSchedule, PeriodicTask from rest_framework_json_api.serializers import ValidationError from tasks.tasks import perform_scheduled_scan_task -from api.models import Provider +from api.db_utils import rls_transaction +from api.models import Provider, Scan, StateChoices def schedule_provider_scan(provider_instance: Provider): + tenant_id = str(provider_instance.tenant_id) + provider_id = str(provider_instance.id) + schedule, _ = IntervalSchedule.objects.get_or_create( every=24, period=IntervalSchedule.HOURS, @@ -17,23 +21,9 @@ def schedule_provider_scan(provider_instance: Provider): # Create a unique name for the periodic task task_name = f"scan-perform-scheduled-{provider_instance.id}" - # Schedule the task - _, created = PeriodicTask.objects.get_or_create( - interval=schedule, - name=task_name, - task="scan-perform-scheduled", - kwargs=json.dumps( - { - "tenant_id": str(provider_instance.tenant_id), - "provider_id": str(provider_instance.id), - } - ), - one_off=False, - defaults={ - "start_time": datetime.now(timezone.utc) + timedelta(hours=24), - }, - ) - if not created: + if PeriodicTask.objects.filter( + interval=schedule, name=task_name, task="scan-perform-scheduled" + ).exists(): raise ValidationError( [ { @@ -45,9 +35,36 @@ def schedule_provider_scan(provider_instance: Provider): ] ) + with rls_transaction(tenant_id): + scheduled_scan = Scan.objects.create( + tenant_id=tenant_id, + name="Daily scheduled scan", + provider_id=provider_id, + trigger=Scan.TriggerChoices.SCHEDULED, + state=StateChoices.AVAILABLE, + scheduled_at=datetime.now(timezone.utc), + ) + + # Schedule the task + periodic_task_instance = PeriodicTask.objects.create( + interval=schedule, + name=task_name, + task="scan-perform-scheduled", + kwargs=json.dumps( + { + "tenant_id": tenant_id, + "provider_id": provider_id, + } + ), + one_off=False, + start_time=datetime.now(timezone.utc) + timedelta(hours=24), + ) + scheduled_scan.scheduler_task_id = periodic_task_instance.id + scheduled_scan.save() + return perform_scheduled_scan_task.apply_async( kwargs={ "tenant_id": str(provider_instance.tenant_id), - "provider_id": str(provider_instance.id), + "provider_id": provider_id, }, ) diff --git a/api/src/backend/tasks/jobs/scan.py b/api/src/backend/tasks/jobs/scan.py index b11366a859..ad11079ef0 100644 --- a/api/src/backend/tasks/jobs/scan.py +++ b/api/src/backend/tasks/jobs/scan.py @@ -245,8 +245,11 @@ def perform_prowler_scan( status = FindingStatus[finding.status] delta = _create_finding_delta(last_status, status) - # For the findings prior to the change, when a first finding is found with delta!="new" it will be assigned a current date as first_seen_at and the successive findings with the same UID will always get the date of the previous finding. - # For new findings, when a finding (delta="new") is found for the first time, the first_seen_at attribute will be assigned the current date, the following findings will get that date. + # For the findings prior to the change, when a first finding is found with delta!="new" it will be + # assigned a current date as first_seen_at and the successive findings with the same UID will + # always get the date of the previous finding. + # For new findings, when a finding (delta="new") is found for the first time, the first_seen_at + # attribute will be assigned the current date, the following findings will get that date. if not last_first_seen_at: last_first_seen_at = datetime.now(tz=timezone.utc) diff --git a/api/src/backend/tasks/tasks.py b/api/src/backend/tasks/tasks.py index 1f87e0fdeb..792900c4e7 100644 --- a/api/src/backend/tasks/tasks.py +++ b/api/src/backend/tasks/tasks.py @@ -1,15 +1,14 @@ -from datetime import datetime, timedelta, timezone - from celery import shared_task from config.celery import RLSTask from django_celery_beat.models import PeriodicTask from tasks.jobs.connection import check_provider_connection from tasks.jobs.deletion import delete_provider, delete_tenant from tasks.jobs.scan import aggregate_findings, perform_prowler_scan +from tasks.utils import get_next_execution_datetime from api.db_utils import rls_transaction from api.decorators import set_tenant -from api.models import Provider, Scan +from api.models import Scan, StateChoices @shared_task(base=RLSTask, name="provider-connection-check") @@ -100,28 +99,42 @@ def perform_scheduled_scan_task(self, tenant_id: str, provider_id: str): task_id = self.request.id with rls_transaction(tenant_id): - provider_instance = Provider.objects.get(pk=provider_id) periodic_task_instance = PeriodicTask.objects.get( name=f"scan-perform-scheduled-{provider_id}" ) - next_scan_date = datetime.combine( - datetime.now(timezone.utc), periodic_task_instance.start_time.time() - ) + timedelta(hours=24) - - scan_instance = Scan.objects.create( + next_scan_datetime = get_next_execution_datetime(task_id, provider_id) + scan_instance, _ = Scan.objects.get_or_create( tenant_id=tenant_id, - name="Daily scheduled scan", - provider=provider_instance, + provider_id=provider_id, trigger=Scan.TriggerChoices.SCHEDULED, - next_scan_at=next_scan_date, - task_id=task_id, + state__in=(StateChoices.SCHEDULED, StateChoices.AVAILABLE), + scheduler_task_id=periodic_task_instance.id, + defaults={"state": StateChoices.SCHEDULED}, ) - result = perform_prowler_scan( - tenant_id=tenant_id, - scan_id=str(scan_instance.id), - provider_id=provider_id, - ) + scan_instance.task_id = task_id + scan_instance.save() + + try: + result = perform_prowler_scan( + tenant_id=tenant_id, + scan_id=str(scan_instance.id), + provider_id=provider_id, + ) + except Exception as e: + raise e + finally: + with rls_transaction(tenant_id): + Scan.objects.get_or_create( + tenant_id=tenant_id, + name="Daily scheduled scan", + provider_id=provider_id, + trigger=Scan.TriggerChoices.SCHEDULED, + state=StateChoices.SCHEDULED, + scheduled_at=next_scan_datetime, + scheduler_task_id=periodic_task_instance.id, + ) + perform_scan_summary_task.apply_async( kwargs={ "tenant_id": tenant_id, diff --git a/api/src/backend/tasks/tests/test_beat.py b/api/src/backend/tasks/tests/test_beat.py index 78b5acb039..6e5c6fdf91 100644 --- a/api/src/backend/tasks/tests/test_beat.py +++ b/api/src/backend/tasks/tests/test_beat.py @@ -6,6 +6,8 @@ from django_celery_beat.models import IntervalSchedule, PeriodicTask from rest_framework_json_api.serializers import ValidationError from tasks.beat import schedule_provider_scan +from api.models import Scan + @pytest.mark.django_db class TestScheduleProviderScan: @@ -15,9 +17,11 @@ class TestScheduleProviderScan: with patch( "tasks.tasks.perform_scheduled_scan_task.apply_async" ) as mock_apply_async: + assert Scan.all_objects.count() == 0 result = schedule_provider_scan(provider_instance) assert result is not None + assert Scan.all_objects.count() == 1 mock_apply_async.assert_called_once_with( kwargs={ diff --git a/api/src/backend/tasks/tests/test_utils.py b/api/src/backend/tasks/tests/test_utils.py new file mode 100644 index 0000000000..619626af9e --- /dev/null +++ b/api/src/backend/tasks/tests/test_utils.py @@ -0,0 +1,76 @@ +from datetime import datetime, timedelta, timezone +from unittest.mock import patch + +import pytest +from django_celery_beat.models import IntervalSchedule, PeriodicTask +from django_celery_results.models import TaskResult +from tasks.utils import get_next_execution_datetime + + +@pytest.mark.django_db +class TestGetNextExecutionDatetime: + @pytest.fixture + def setup_periodic_task(self, db): + # Create a periodic task with an hourly interval + interval = IntervalSchedule.objects.create( + every=1, period=IntervalSchedule.HOURS + ) + periodic_task = PeriodicTask.objects.create( + name="scan-perform-scheduled-123", + task="scan-perform-scheduled", + interval=interval, + ) + return periodic_task + + @pytest.fixture + def setup_task_result(self, db): + # Create a task result record + task_result = TaskResult.objects.create( + task_id="abc123", + task_name="scan-perform-scheduled", + status="SUCCESS", + date_created=datetime.now(timezone.utc) - timedelta(hours=1), + result="Success", + ) + return task_result + + def test_get_next_execution_datetime_success( + self, setup_task_result, setup_periodic_task + ): + task_result = setup_task_result + periodic_task = setup_periodic_task + + # Mock periodic_task_name on TaskResult + with patch.object( + TaskResult, "periodic_task_name", return_value=periodic_task.name + ): + next_execution = get_next_execution_datetime( + task_id=task_result.task_id, provider_id="123" + ) + + expected_time = task_result.date_created + timedelta(hours=1) + assert next_execution == expected_time + + def test_get_next_execution_datetime_fallback_to_provider_id( + self, setup_task_result, setup_periodic_task + ): + task_result = setup_task_result + + # Simulate the case where `periodic_task_name` is missing + with patch.object(TaskResult, "periodic_task_name", return_value=None): + next_execution = get_next_execution_datetime( + task_id=task_result.task_id, provider_id="123" + ) + + expected_time = task_result.date_created + timedelta(hours=1) + assert next_execution == expected_time + + def test_get_next_execution_datetime_periodic_task_does_not_exist( + self, setup_task_result + ): + task_result = setup_task_result + + with pytest.raises(PeriodicTask.DoesNotExist): + get_next_execution_datetime( + task_id=task_result.task_id, provider_id="nonexistent" + ) diff --git a/api/src/backend/tasks/utils.py b/api/src/backend/tasks/utils.py new file mode 100644 index 0000000000..5ac78a03ab --- /dev/null +++ b/api/src/backend/tasks/utils.py @@ -0,0 +1,26 @@ +from datetime import datetime, timedelta, timezone + +from django_celery_beat.models import PeriodicTask +from django_celery_results.models import TaskResult + + +def get_next_execution_datetime(task_id: int, provider_id: str) -> datetime: + task_instance = TaskResult.objects.get(task_id=task_id) + try: + periodic_task_instance = PeriodicTask.objects.get( + name=task_instance.periodic_task_name + ) + except PeriodicTask.DoesNotExist: + periodic_task_instance = PeriodicTask.objects.get( + name=f"scan-perform-scheduled-{provider_id}" + ) + + interval = periodic_task_instance.interval + + current_scheduled_time = datetime.combine( + datetime.now(timezone.utc).date(), + task_instance.date_created.time(), + tzinfo=timezone.utc, + ) + + return current_scheduled_time + timedelta(**{interval.period: interval.every}) From 6a954267499d55076c11d109f637dfea4cd511af Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 3 Feb 2025 11:51:07 +0100 Subject: [PATCH 11/31] fix(findings): order findings by inserted_at DESC (#6782) --- ui/app/(prowler)/findings/page.tsx | 7 +++---- ui/app/(prowler)/page.tsx | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ui/app/(prowler)/findings/page.tsx b/ui/app/(prowler)/findings/page.tsx index 0d35cac8c4..f619bccde9 100644 --- a/ui/app/(prowler)/findings/page.tsx +++ b/ui/app/(prowler)/findings/page.tsx @@ -26,11 +26,10 @@ export default async function Findings({ searchParams: SearchParamsProps; }) { const searchParamsKey = JSON.stringify(searchParams || {}); - const defaultSort = "severity,status"; - const sort = searchParams.sort?.toString() || defaultSort; + const sort = searchParams.sort?.toString(); // Make sure the sort is correctly encoded - const encodedSort = sort.replace(/^\+/, ""); + const encodedSort = sort?.replace(/^\+/, ""); // Extract all filter parameters and combine with default filters const defaultFilters = { @@ -137,7 +136,7 @@ const SSRDataTable = async ({ searchParams: SearchParamsProps; }) => { const page = parseInt(searchParams.page?.toString() || "1", 10); - const defaultSort = "severity,status"; + const defaultSort = "severity,status,-inserted_at"; const sort = searchParams.sort?.toString() || defaultSort; // Make sure the sort is correctly encoded diff --git a/ui/app/(prowler)/page.tsx b/ui/app/(prowler)/page.tsx index ff2ab3d28c..c6e0748362 100644 --- a/ui/app/(prowler)/page.tsx +++ b/ui/app/(prowler)/page.tsx @@ -129,7 +129,7 @@ const SSRFindingsBySeverity = async ({ const SSRDataNewFindingsTable = async () => { const page = 1; - const sort = "severity,updated_at"; + const sort = "severity,-inserted_at"; const defaultFilters = { "filter[status__in]": "FAIL", From bb4685cf90aa07f8d4523cbcd98067ab8a9a9a53 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 3 Feb 2025 15:20:18 +0100 Subject: [PATCH 12/31] fix(findings): remove default status filtering (#6784) --- ui/app/(prowler)/findings/page.tsx | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/ui/app/(prowler)/findings/page.tsx b/ui/app/(prowler)/findings/page.tsx index f619bccde9..39d2ad7846 100644 --- a/ui/app/(prowler)/findings/page.tsx +++ b/ui/app/(prowler)/findings/page.tsx @@ -31,15 +31,14 @@ export default async function Findings({ // Make sure the sort is correctly encoded const encodedSort = sort?.replace(/^\+/, ""); - // Extract all filter parameters and combine with default filters - const defaultFilters = { - "filter[status__in]": "FAIL, PASS", - }; - const filters: Record = { - ...defaultFilters, ...Object.fromEntries( - Object.entries(searchParams).filter(([key]) => key.startsWith("filter[")), + Object.entries(searchParams) + .filter(([key]) => key.startsWith("filter[")) + .map(([key, value]) => [ + key, + Array.isArray(value) ? value.join(",") : value?.toString() || "", + ]), ), }; @@ -142,15 +141,14 @@ const SSRDataTable = async ({ // Make sure the sort is correctly encoded const encodedSort = sort.replace(/^\+/, ""); - // Extract all filter parameters and combine with default filters - const defaultFilters = { - "filter[status__in]": "FAIL, PASS", - }; - const filters: Record = { - ...defaultFilters, ...Object.fromEntries( - Object.entries(searchParams).filter(([key]) => key.startsWith("filter[")), + Object.entries(searchParams) + .filter(([key]) => key.startsWith("filter[")) + .map(([key, value]) => [ + key, + Array.isArray(value) ? value.join(",") : value?.toString() || "", + ]), ), }; From c0d7c9fc7d8300a7266df6fa24aa7e8a519f8295 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 09:27:17 -0500 Subject: [PATCH 13/31] chore(deps): bump google-api-python-client from 2.159.0 to 2.160.0 (#6720) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index cbb2504f70..0772ec464a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1692,13 +1692,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-api-python-client" -version = "2.159.0" +version = "2.160.0" description = "Google API Client Library for Python" optional = false python-versions = ">=3.7" files = [ - {file = "google_api_python_client-2.159.0-py2.py3-none-any.whl", hash = "sha256:baef0bb631a60a0bd7c0bf12a5499e3a40cd4388484de7ee55c1950bf820a0cf"}, - {file = "google_api_python_client-2.159.0.tar.gz", hash = "sha256:55197f430f25c907394b44fa078545ffef89d33fd4dca501b7db9f0d8e224bd6"}, + {file = "google_api_python_client-2.160.0-py2.py3-none-any.whl", hash = "sha256:63d61fb3e4cf3fb31a70a87f45567c22f6dfe87bbfa27252317e3e2c42900db4"}, + {file = "google_api_python_client-2.160.0.tar.gz", hash = "sha256:a8ccafaecfa42d15d5b5c3134ced8de08380019717fc9fb1ed510ca58eca3b7e"}, ] [package.dependencies] @@ -5090,4 +5090,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.13" -content-hash = "19e7c4654f280256e53757abd04cb63938d34ae4d8305a77f21e75c891b5376d" +content-hash = "c2620b4dedb78560320e7b977e3e481a1179cc4317e3b862ac869de83af868f2" diff --git a/pyproject.toml b/pyproject.toml index 9cb926c96e..a747c0f208 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ cryptography = "43.0.1" dash = "2.18.2" dash-bootstrap-components = "1.6.0" detect-secrets = "1.5.0" -google-api-python-client = "2.159.0" +google-api-python-client = "2.160.0" google-auth-httplib2 = ">=0.1,<0.3" jsonschema = "4.23.0" kubernetes = "31.0.0" From 867b371522387071859a77b16fa74f7849a3ff33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Mon, 3 Feb 2025 15:28:04 +0100 Subject: [PATCH 14/31] fix(cis_2.0_aws): add checks to needed reqs (#6694) --- prowler/compliance/aws/cis_2.0_aws.json | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/prowler/compliance/aws/cis_2.0_aws.json b/prowler/compliance/aws/cis_2.0_aws.json index 09f2ebe08f..1d12e296d5 100644 --- a/prowler/compliance/aws/cis_2.0_aws.json +++ b/prowler/compliance/aws/cis_2.0_aws.json @@ -303,7 +303,9 @@ { "Id": "1.22", "Description": "Ensure access to AWSCloudShellFullAccess is restricted", - "Checks": [], + "Checks": [ + "iam_policy_cloudshell_admin_not_attached" + ], "Attributes": [ { "Section": "1. Identity and Access Management", @@ -492,7 +494,8 @@ "Id": "2.1.2", "Description": "Ensure MFA Delete is enabled on S3 buckets", "Checks": [ - "s3_bucket_no_mfa_delete" + "s3_bucket_no_mfa_delete", + "cloudtrail_bucket_requires_mfa_delete" ], "Attributes": [ { @@ -581,7 +584,8 @@ "Id": "2.3.1", "Description": "Ensure that encryption is enabled for RDS Instances", "Checks": [ - "rds_instance_storage_encrypted" + "rds_instance_storage_encrypted", + "rds_instance_transport_encrypted" ], "Attributes": [ { @@ -1347,7 +1351,8 @@ "Id": "5.6", "Description": "Ensure that EC2 Metadata Service only allows IMDSv2", "Checks": [ - "ec2_instance_imdsv2_enabled" + "ec2_instance_imdsv2_enabled", + "ec2_instance_account_imdsv2_enabled" ], "Attributes": [ { From db8b4727295cbb34b2637331ea89f02da35e609d Mon Sep 17 00:00:00 2001 From: secretcod3r <101349794+secretcod3r@users.noreply.github.com> Date: Mon, 3 Feb 2025 16:29:08 +0200 Subject: [PATCH 15/31] fix(gcp): fix wrong provider value in check (#6691) --- .../compute_public_address_shodan.metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prowler/providers/gcp/services/compute/compute_public_address_shodan/compute_public_address_shodan.metadata.json b/prowler/providers/gcp/services/compute/compute_public_address_shodan/compute_public_address_shodan.metadata.json index 5c19d5d1f7..d897b42a1f 100644 --- a/prowler/providers/gcp/services/compute/compute_public_address_shodan/compute_public_address_shodan.metadata.json +++ b/prowler/providers/gcp/services/compute/compute_public_address_shodan/compute_public_address_shodan.metadata.json @@ -1,5 +1,5 @@ { - "Provider": "compute", + "Provider": "gcp", "CheckID": "compute_public_address_shodan", "CheckTitle": "Check if any of the Public Addresses are in Shodan (requires Shodan API KEY).", "CheckType": [ From 1b6f9332f18fddf252ec212f8c05b714dc4733d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 09:35:53 -0500 Subject: [PATCH 16/31] chore(deps): bump trufflesecurity/trufflehog from 3.88.2 to 3.88.4 (#6760) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/find-secrets.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/find-secrets.yml b/.github/workflows/find-secrets.yml index b39eb8cdc5..0eb7bbd6fe 100644 --- a/.github/workflows/find-secrets.yml +++ b/.github/workflows/find-secrets.yml @@ -11,7 +11,7 @@ jobs: with: fetch-depth: 0 - name: TruffleHog OSS - uses: trufflesecurity/trufflehog@v3.88.2 + uses: trufflesecurity/trufflehog@v3.88.4 with: path: ./ base: ${{ github.event.repository.default_branch }} From 8e784a5b6d6373e29b3ba75d03d672c9e460aaf8 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 3 Feb 2025 16:08:47 +0100 Subject: [PATCH 17/31] feat(scans): show scan details right after launch (#6791) --- ui/app/(prowler)/scans/page.tsx | 3 --- ui/components/scans/index.ts | 1 - ui/components/scans/scan-warning-bar.tsx | 18 ------------------ ui/components/scans/table/scan-detail.tsx | 8 +++----- .../scans/table/scans/column-get-scans.tsx | 8 ++++---- 5 files changed, 7 insertions(+), 31 deletions(-) delete mode 100644 ui/components/scans/scan-warning-bar.tsx diff --git a/ui/app/(prowler)/scans/page.tsx b/ui/app/(prowler)/scans/page.tsx index 9781627118..db38bd4e97 100644 --- a/ui/app/(prowler)/scans/page.tsx +++ b/ui/app/(prowler)/scans/page.tsx @@ -8,7 +8,6 @@ import { ButtonRefreshData, NoProvidersAdded, NoProvidersConnected, - ScanWarningBar, } from "@/components/scans"; import { LaunchScanWorkflow } from "@/components/scans/launch-workflow"; import { SkeletonTableScans } from "@/components/scans/table"; @@ -73,8 +72,6 @@ export default async function Scans({
- - )} diff --git a/ui/components/scans/index.ts b/ui/components/scans/index.ts index 98157857ab..aa42edfe3c 100644 --- a/ui/components/scans/index.ts +++ b/ui/components/scans/index.ts @@ -2,4 +2,3 @@ export * from "./button-refresh-data"; export * from "./link-to-findings-from-scan"; export * from "./no-providers-added"; export * from "./no-providers-connected"; -export * from "./scan-warning-bar"; diff --git a/ui/components/scans/scan-warning-bar.tsx b/ui/components/scans/scan-warning-bar.tsx deleted file mode 100644 index 4c9acbb978..0000000000 --- a/ui/components/scans/scan-warning-bar.tsx +++ /dev/null @@ -1,18 +0,0 @@ -"use client"; - -import { InfoIcon } from "../icons"; - -export const ScanWarningBar = () => { - return ( -
- -
- Waiting for Your Scan to Show Up? -

- It may take a few minutes for the scan to appear on the table and be - displayed. -

-
-
- ); -}; diff --git a/ui/components/scans/table/scan-detail.tsx b/ui/components/scans/table/scan-detail.tsx index 6e9c3e288c..7307ff53f0 100644 --- a/ui/components/scans/table/scan-detail.tsx +++ b/ui/components/scans/table/scan-detail.tsx @@ -117,11 +117,9 @@ export const ScanDetail = ({ - {scan.next_scan_at && ( - - - - )} + + + diff --git a/ui/components/scans/table/scans/column-get-scans.tsx b/ui/components/scans/table/scans/column-get-scans.tsx index 55087a0e66..72a70f9311 100644 --- a/ui/components/scans/table/scans/column-get-scans.tsx +++ b/ui/components/scans/table/scans/column-get-scans.tsx @@ -123,13 +123,13 @@ export const ColumnGetScans: ColumnDef[] = [ }, }, { - accessorKey: "next_scan_at", - header: "Next scan", + accessorKey: "scheduled_at", + header: "Scheduled at", cell: ({ row }) => { const { - attributes: { next_scan_at }, + attributes: { scheduled_at }, } = getScanData(row); - return ; + return ; }, }, { From a73ffe5642efe946440ab14af7d1c13d9f9dc9da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Mon, 3 Feb 2025 16:32:10 +0100 Subject: [PATCH 18/31] fix(cis_1.4_aws): add checks to needed reqs (#6696) Co-authored-by: Sergio Garcia --- prowler/compliance/aws/cis_1.4_aws.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/prowler/compliance/aws/cis_1.4_aws.json b/prowler/compliance/aws/cis_1.4_aws.json index da77feb991..584ca284c6 100644 --- a/prowler/compliance/aws/cis_1.4_aws.json +++ b/prowler/compliance/aws/cis_1.4_aws.json @@ -584,7 +584,8 @@ "Id": "2.3.1", "Description": "Ensure that encryption is enabled for RDS Instances", "Checks": [ - "rds_instance_storage_encrypted" + "rds_instance_storage_encrypted", + "rds_instance_transport_encrypted" ], "Attributes": [ { From 072828512a34433a9229daf022dc5b009dadf8c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Mon, 3 Feb 2025 16:32:20 +0100 Subject: [PATCH 19/31] fix(cis_1.5_aws): add checks to needed reqs (#6695) Co-authored-by: Sergio Garcia --- prowler/compliance/aws/cis_1.5_aws.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/prowler/compliance/aws/cis_1.5_aws.json b/prowler/compliance/aws/cis_1.5_aws.json index 6d5283b81c..90ca3742e4 100644 --- a/prowler/compliance/aws/cis_1.5_aws.json +++ b/prowler/compliance/aws/cis_1.5_aws.json @@ -584,7 +584,8 @@ "Id": "2.3.1", "Description": "Ensure that encryption is enabled for RDS Instances", "Checks": [ - "rds_instance_storage_encrypted" + "rds_instance_storage_encrypted", + "rds_instance_transport_encrypted" ], "Attributes": [ { From 3858cccc417aa6ca33f4926985d727c82c87e366 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 10:32:42 -0500 Subject: [PATCH 20/31] chore(deps-dev): bump pylint from 3.3.3 to 3.3.4 (#6721) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0772ec464a..ce20b23af4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3635,13 +3635,13 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pylint" -version = "3.3.3" +version = "3.3.4" description = "python code static checker" optional = false python-versions = ">=3.9.0" files = [ - {file = "pylint-3.3.3-py3-none-any.whl", hash = "sha256:26e271a2bc8bce0fc23833805a9076dd9b4d5194e2a02164942cb3cdc37b4183"}, - {file = "pylint-3.3.3.tar.gz", hash = "sha256:07c607523b17e6d16e2ae0d7ef59602e332caa762af64203c24b41c27139f36a"}, + {file = "pylint-3.3.4-py3-none-any.whl", hash = "sha256:289e6a1eb27b453b08436478391a48cd53bb0efb824873f949e709350f3de018"}, + {file = "pylint-3.3.4.tar.gz", hash = "sha256:74ae7a38b177e69a9b525d0794bd8183820bfa7eb68cc1bee6e8ed22a42be4ce"}, ] [package.dependencies] @@ -3652,7 +3652,7 @@ dill = [ {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, ] -isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" +isort = ">=4.2.5,<5.13.0 || >5.13.0,<7" mccabe = ">=0.6,<0.8" platformdirs = ">=2.2.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} @@ -5090,4 +5090,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.13" -content-hash = "c2620b4dedb78560320e7b977e3e481a1179cc4317e3b862ac869de83af868f2" +content-hash = "862dc654b14d6b85f9d8951bbf476b3d9f2fe60b5af1de0e7a69337178244584" diff --git a/pyproject.toml b/pyproject.toml index a747c0f208..c881814f81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,7 +85,7 @@ mock = "5.1.0" moto = {extras = ["all"], version = "5.0.27"} openapi-schema-validator = "0.6.3" openapi-spec-validator = "0.7.1" -pylint = "3.3.3" +pylint = "3.3.4" pytest = "8.3.4" pytest-cov = "6.0.0" pytest-env = "1.1.5" From a96cc92d77186b6ffd39656a8da88f3ea61a6dba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 11:37:02 -0500 Subject: [PATCH 21/31] chore(deps-dev): bump mkdocs-material from 9.5.50 to 9.6.2 (#6799) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index ce20b23af4..ef99dd9c0d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2536,13 +2536,13 @@ dev = ["click", "codecov", "mkdocs-gen-files", "mkdocs-git-authors-plugin", "mkd [[package]] name = "mkdocs-material" -version = "9.5.50" +version = "9.6.2" description = "Documentation that simply works" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.5.50-py3-none-any.whl", hash = "sha256:f24100f234741f4d423a9d672a909d859668a4f404796be3cf035f10d6050385"}, - {file = "mkdocs_material-9.5.50.tar.gz", hash = "sha256:ae5fe16f3d7c9ccd05bb6916a7da7420cf99a9ce5e33debd9d40403a090d5825"}, + {file = "mkdocs_material-9.6.2-py3-none-any.whl", hash = "sha256:71d90dbd63b393ad11a4d90151dfe3dcbfcd802c0f29ce80bebd9bbac6abc753"}, + {file = "mkdocs_material-9.6.2.tar.gz", hash = "sha256:a3de1c5d4c745f10afa78b1a02f917b9dce0808fb206adc0f5bb48b58c1ca21f"}, ] [package.dependencies] @@ -5090,4 +5090,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.13" -content-hash = "862dc654b14d6b85f9d8951bbf476b3d9f2fe60b5af1de0e7a69337178244584" +content-hash = "3d3b3f7704292b140d005732ab1ea87cfeeac209889cc941b31eeec486cb268e" diff --git a/pyproject.toml b/pyproject.toml index c881814f81..9a6a04df43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,7 +100,7 @@ optional = true [tool.poetry.group.docs.dependencies] mkdocs = "1.6.1" mkdocs-git-revision-date-localized-plugin = "1.3.0" -mkdocs-material = "9.5.50" +mkdocs-material = "9.6.2" mkdocs-material-extensions = "1.3.1" [tool.poetry.scripts] From d64f611f519d7f2237dfb1ebc249e798525bd986 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 12:48:18 -0500 Subject: [PATCH 22/31] chore(deps): bump pytz from 2024.2 to 2025.1 (#6765) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index ef99dd9c0d..1a3d754e90 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3804,13 +3804,13 @@ six = ">=1.5" [[package]] name = "pytz" -version = "2024.2" +version = "2025.1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ - {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, - {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, + {file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"}, + {file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"}, ] [[package]] @@ -5090,4 +5090,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.13" -content-hash = "3d3b3f7704292b140d005732ab1ea87cfeeac209889cc941b31eeec486cb268e" +content-hash = "c387413f38df1dc2286d8e16077fdccd2573943bf7cbdaeca2389be0ee0c339c" diff --git a/pyproject.toml b/pyproject.toml index 9a6a04df43..b6d6091419 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,7 @@ py-ocsf-models = "0.2.0" pydantic = "1.10.18" python = ">=3.9,<3.13" python-dateutil = "^2.9.0.post0" -pytz = "2024.2" +pytz = "2025.1" schema = "0.7.7" shodan = "1.31.0" slack-sdk = "3.34.0" From 552aa64741d700fae9c96ff58cea67d0db3c2c30 Mon Sep 17 00:00:00 2001 From: Drew Kerrigan Date: Mon, 3 Feb 2025 14:51:03 -0500 Subject: [PATCH 23/31] docs(): add description of changed and new delta values to prowler app tutorial (#6801) --- docs/tutorials/prowler-app.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/tutorials/prowler-app.md b/docs/tutorials/prowler-app.md index 40a40ed096..3184d9e958 100644 --- a/docs/tutorials/prowler-app.md +++ b/docs/tutorials/prowler-app.md @@ -133,3 +133,5 @@ While the scan is running, start exploring the findings in these sections: Issues - **Browse All Findings**: Detailed list of findings detected, where you can filter by severity, service, and more. Findings + +To view all `new` findings that have not been seen prior to this scan, click the `Delta` filter and select `new`. To view all `changed` findings that have had a status change (from `PASS` to `FAIL` for example), click the `Delta` filter and select `changed`. From 5f54377407f05dc2145356c4fc54ca89a5b154f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Mon, 3 Feb 2025 20:59:08 +0100 Subject: [PATCH 24/31] chore(aws_audit_manager_control_tower_guardrails): add checks to reqs (#6699) --- ..._manager_control_tower_guardrails_aws.json | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/prowler/compliance/aws/aws_audit_manager_control_tower_guardrails_aws.json b/prowler/compliance/aws/aws_audit_manager_control_tower_guardrails_aws.json index 34a5856fd7..fa4d71c05b 100644 --- a/prowler/compliance/aws/aws_audit_manager_control_tower_guardrails_aws.json +++ b/prowler/compliance/aws/aws_audit_manager_control_tower_guardrails_aws.json @@ -28,7 +28,9 @@ "Service": "ebs" } ], - "Checks": [] + "Checks": [ + "ec2_ebs_volume_snapshots_exists" + ] }, { "Id": "1.0.3", @@ -42,7 +44,8 @@ } ], "Checks": [ - "ec2_ebs_default_encryption" + "ec2_ebs_default_encryption", + "ec2_ebs_volume_encryption" ] }, { @@ -87,7 +90,9 @@ } ], "Checks": [ - "iam_user_mfa_enabled_console_access" + "iam_user_mfa_enabled_console_access", + "iam_user_hardware_mfa_enabled", + "iam_root_mfa_enabled" ] }, { @@ -102,7 +107,9 @@ } ], "Checks": [ - "iam_user_mfa_enabled_console_access" + "iam_user_mfa_enabled_console_access", + "iam_user_hardware_mfa_enabled", + "iam_root_mfa_enabled" ] }, { @@ -117,7 +124,9 @@ } ], "Checks": [ - "iam_root_mfa_enabled" + "iam_root_mfa_enabled", + "iam_root_hardware_mfa_enabled", + "iam_user_mfa_enabled_console_access" ] }, { @@ -162,7 +171,10 @@ } ], "Checks": [ - "rds_instance_no_public_access" + "rds_instance_no_public_access", + "s3_bucket_public_access", + "s3_bucket_public_list_acl", + "s3_account_level_public_access_blocks" ] }, { @@ -192,7 +204,8 @@ } ], "Checks": [ - "rds_instance_storage_encrypted" + "rds_instance_storage_encrypted", + "rds_instance_transport_encrypted" ] }, { From acfbaf75d59f790b3f077dd9065f612d21b02917 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 4 Feb 2025 12:46:07 +0100 Subject: [PATCH 25/31] chore(forms): improvements to the sign-in and sign-up forms (#6813) --- ui/components/auth/oss/auth-form.tsx | 53 +++++++++++++++++++++--- ui/components/ui/custom/custom-input.tsx | 6 ++- ui/types/authFormSchema.ts | 16 +++++-- 3 files changed, 66 insertions(+), 9 deletions(-) diff --git a/ui/components/auth/oss/auth-form.tsx b/ui/components/auth/oss/auth-form.tsx index e215abad4f..9a3d1985b9 100644 --- a/ui/components/auth/oss/auth-form.tsx +++ b/ui/components/auth/oss/auth-form.tsx @@ -1,7 +1,7 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import { Link } from "@nextui-org/react"; +import { Checkbox, Link, Spacer } from "@nextui-org/react"; import { useRouter } from "next/navigation"; import { useForm } from "react-hook-form"; import { z } from "zod"; @@ -11,7 +11,12 @@ import { NotificationIcon, ProwlerExtended } from "@/components/icons"; import { ThemeSwitch } from "@/components/ThemeSwitch"; import { useToast } from "@/components/ui"; import { CustomButton, CustomInput } from "@/components/ui/custom"; -import { Form } from "@/components/ui/form"; +import { + Form, + FormControl, + FormField, + FormMessage, +} from "@/components/ui/form"; import { ApiError, authFormSchema } from "@/types"; export const AuthForm = ({ @@ -167,6 +172,7 @@ export const AuthForm = ({ /> )} + {/* {type === "sign-in" && ( @@ -213,16 +223,49 @@ export const AuthForm = ({ isDisabled={invitationToken !== null && true} /> )} + + {process.env.NEXT_PUBLIC_IS_CLOUD_ENV === "true" && ( + ( + <> + + field.onChange(e.target.checked)} + > + I agree with the  + + Terms of Service + +  of Prowler + + + + + )} + /> + )} )} - {form.formState.errors?.email && ( + {type === "sign-in" && form.formState.errors?.email && (
-

No user found

+

Invalid email or password

)} + {type === "sign-in" && } + { isRequired?: boolean; isInvalid?: boolean; isDisabled?: boolean; + showFormMessage?: boolean; } export const CustomInput = ({ @@ -41,6 +42,7 @@ export const CustomInput = ({ isRequired = true, isInvalid, isDisabled = false, + showFormMessage = true, }: CustomInputProps) => { const [isPasswordVisible, setIsPasswordVisible] = useState(false); const [isConfirmPasswordVisible, setIsConfirmPasswordVisible] = @@ -112,7 +114,9 @@ export const CustomInput = ({ {...field} /> - + {showFormMessage && ( + + )} )} /> diff --git a/ui/types/authFormSchema.ts b/ui/types/authFormSchema.ts index ac0fc46dcf..58737a1f8a 100644 --- a/ui/types/authFormSchema.ts +++ b/ui/types/authFormSchema.ts @@ -24,11 +24,21 @@ export const authFormSchema = (type: string) => invitationToken: type === "sign-in" ? z.string().optional() : z.string().optional(), + termsAndConditions: + type === "sign-in" || process.env.NEXT_PUBLIC_IS_CLOUD_ENV !== "true" + ? z.boolean().optional() + : z.boolean().refine((value) => value === true, { + message: "You must accept the terms and conditions.", + }), + // Fields for Sign In and Sign Up email: z.string().email(), - password: z.string().min(12, { - message: "It must contain at least 12 characters.", - }), + password: + type === "sign-in" + ? z.string() + : z.string().min(12, { + message: "It must contain at least 12 characters.", + }), }) .refine( (data) => type === "sign-in" || data.password === data.confirmPassword, From 80267c389b3feaa816c1c8289c26ff28a9e4d986 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 4 Feb 2025 13:20:24 +0100 Subject: [PATCH 26/31] style(forms): improve spacing consistency (#6814) --- ui/components/auth/oss/auth-form.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ui/components/auth/oss/auth-form.tsx b/ui/components/auth/oss/auth-form.tsx index 9a3d1985b9..967bbdd352 100644 --- a/ui/components/auth/oss/auth-form.tsx +++ b/ui/components/auth/oss/auth-form.tsx @@ -1,7 +1,7 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import { Checkbox, Link, Spacer } from "@nextui-org/react"; +import { Checkbox, Link } from "@nextui-org/react"; import { useRouter } from "next/navigation"; import { useForm } from "react-hook-form"; import { z } from "zod"; @@ -148,7 +148,7 @@ export const AuthForm = ({
{type === "sign-up" && ( @@ -258,14 +258,12 @@ export const AuthForm = ({ )} {type === "sign-in" && form.formState.errors?.email && ( -
+

Invalid email or password

)} - {type === "sign-in" && } - Date: Tue, 4 Feb 2025 12:58:48 -0500 Subject: [PATCH 27/31] chore(deps-dev): bump moto from 5.0.27 to 5.0.28 (#6804) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: MrCloudSec --- poetry.lock | 21 +++++++-------------- pyproject.toml | 4 ++-- tests/lib/outputs/ocsf/ocsf_test.py | 2 +- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1a3d754e90..93e5fc2cfe 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2050,8 +2050,6 @@ optional = false python-versions = "*" files = [ {file = "jsonpath-ng-1.7.0.tar.gz", hash = "sha256:f6f5f7fd4e5ff79c785f1573b394043b39849fb2bb47bcead935d12b00beab3c"}, - {file = "jsonpath_ng-1.7.0-py2-none-any.whl", hash = "sha256:898c93fc173f0c336784a3fa63d7434297544b7198124a68f9a3ef9597b0ae6e"}, - {file = "jsonpath_ng-1.7.0-py3-none-any.whl", hash = "sha256:f3d7f9e848cba1b6da28c55b1c26ff915dc9e0b1ba7e752a53d6da8d5cbd00b6"}, ] [package.dependencies] @@ -2592,13 +2590,13 @@ test = ["pytest", "pytest-cov"] [[package]] name = "moto" -version = "5.0.27" +version = "5.0.28" description = "A library that allows you to easily mock out tests based on AWS infrastructure" optional = false python-versions = ">=3.8" files = [ - {file = "moto-5.0.27-py3-none-any.whl", hash = "sha256:27042fd94c8def0166d9f2ae8d39d9488d4b3115542b5fca88566c0424549013"}, - {file = "moto-5.0.27.tar.gz", hash = "sha256:6c123de7e0e5e6508a10c399ba3ecf2d5143f263f8e804fd4a7091941c3f5207"}, + {file = "moto-5.0.28-py3-none-any.whl", hash = "sha256:2dfbea1afe3b593e13192059a1a7fc4b3cf7fdf92e432070c22346efa45aa0f0"}, + {file = "moto-5.0.28.tar.gz", hash = "sha256:4d3437693411ec943c13c77de5b0b520c4b0a9ac850fead4ba2a54709e086e8b"}, ] [package.dependencies] @@ -3454,13 +3452,13 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] [[package]] name = "py-ocsf-models" -version = "0.2.0" +version = "0.3.0" description = "This is a Python implementation of the OCSF models. The models are used to represent the data of the OCSF Schema defined in https://schema.ocsf.io/." optional = false python-versions = "<3.13,>=3.9" files = [ - {file = "py_ocsf_models-0.2.0-py3-none-any.whl", hash = "sha256:ac75fd21077694b343ebaad3479194db113c274879b114277560ff287d5cd7b5"}, - {file = "py_ocsf_models-0.2.0.tar.gz", hash = "sha256:3e12648d05329e6776a0e6b1ffea87a3eb60aa7d8cb2c4afd69e5724f443ce03"}, + {file = "py_ocsf_models-0.3.0-py3-none-any.whl", hash = "sha256:3d31e379be5e4271f7faf62dee9c36798559a1f7f98dff142c0e4cfdb35e291c"}, + {file = "py_ocsf_models-0.3.0.tar.gz", hash = "sha256:ad46b7d9761b74010f06a894df2d9541989252b7ff738cd5c7edbf4283df2279"}, ] [package.dependencies] @@ -4310,7 +4308,6 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"}, - {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"}, @@ -4319,7 +4316,6 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"}, - {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"}, @@ -4328,7 +4324,6 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"}, - {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"}, @@ -4337,7 +4332,6 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"}, - {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"}, @@ -4346,7 +4340,6 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"}, - {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"}, {file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"}, @@ -5090,4 +5083,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.13" -content-hash = "c387413f38df1dc2286d8e16077fdccd2573943bf7cbdaeca2389be0ee0c339c" +content-hash = "d4d817d0a3690b7a39222bf79420e3adb764b47c6c1d2ee0c73a7599333114b1" diff --git a/pyproject.toml b/pyproject.toml index b6d6091419..1fb1ab82a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ microsoft-kiota-abstractions = "1.9.1" msgraph-sdk = "1.18.0" numpy = "2.0.2" pandas = "2.2.3" -py-ocsf-models = "0.2.0" +py-ocsf-models = "0.3.0" pydantic = "1.10.18" python = ">=3.9,<3.13" python-dateutil = "^2.9.0.post0" @@ -82,7 +82,7 @@ docker = "7.1.0" flake8 = "7.1.1" freezegun = "1.5.1" mock = "5.1.0" -moto = {extras = ["all"], version = "5.0.27"} +moto = {extras = ["all"], version = "5.0.28"} openapi-schema-validator = "0.6.3" openapi-spec-validator = "0.7.1" pylint = "3.3.4" diff --git a/tests/lib/outputs/ocsf/ocsf_test.py b/tests/lib/outputs/ocsf/ocsf_test.py index e7bd8b2059..d272849905 100644 --- a/tests/lib/outputs/ocsf/ocsf_test.py +++ b/tests/lib/outputs/ocsf/ocsf_test.py @@ -175,7 +175,7 @@ class TestOCSF: "vendor_name": "Prowler", "version": prowler_version, }, - "version": "1.3.0", + "version": "1.4.0", "profiles": ["cloud", "datetime"], "tenant_uid": "test-organization-id", }, From 2934752eebade0285af9146f9f465470ff29dba4 Mon Sep 17 00:00:00 2001 From: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Date: Tue, 4 Feb 2025 20:28:31 +0100 Subject: [PATCH 28/31] fix(elasticache): InvalidReplicationGroupStateFault error (#6815) Co-authored-by: MrCloudSec --- .../aws/services/elasticache/elasticache_service.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/prowler/providers/aws/services/elasticache/elasticache_service.py b/prowler/providers/aws/services/elasticache/elasticache_service.py index 5fc295630e..6e11a85bc6 100644 --- a/prowler/providers/aws/services/elasticache/elasticache_service.py +++ b/prowler/providers/aws/services/elasticache/elasticache_service.py @@ -147,6 +147,12 @@ class ElastiCache(AWSService): logger.warning( f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + except ( + regional_client.exceptions.InvalidReplicationGroupStateFault + ) as error: + logger.warning( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) except Exception as error: logger.error( f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" @@ -163,6 +169,12 @@ class ElastiCache(AWSService): logger.warning( f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + except ( + regional_client.exceptions.InvalidReplicationGroupStateFault + ) as error: + logger.warning( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) except Exception as error: logger.error( f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" From 9447b33800956e0d758fcfc41445d382cfd65168 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Feb 2025 17:22:51 -0500 Subject: [PATCH 29/31] chore(deps): bump kubernetes from 31.0.0 to 32.0.0 (#6678) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 15 +++++++++++---- pyproject.toml | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 93e5fc2cfe..0f53a98c43 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2050,6 +2050,8 @@ optional = false python-versions = "*" files = [ {file = "jsonpath-ng-1.7.0.tar.gz", hash = "sha256:f6f5f7fd4e5ff79c785f1573b394043b39849fb2bb47bcead935d12b00beab3c"}, + {file = "jsonpath_ng-1.7.0-py2-none-any.whl", hash = "sha256:898c93fc173f0c336784a3fa63d7434297544b7198124a68f9a3ef9597b0ae6e"}, + {file = "jsonpath_ng-1.7.0-py3-none-any.whl", hash = "sha256:f3d7f9e848cba1b6da28c55b1c26ff915dc9e0b1ba7e752a53d6da8d5cbd00b6"}, ] [package.dependencies] @@ -2120,13 +2122,13 @@ referencing = ">=0.31.0" [[package]] name = "kubernetes" -version = "31.0.0" +version = "32.0.0" description = "Kubernetes python client" optional = false python-versions = ">=3.6" files = [ - {file = "kubernetes-31.0.0-py2.py3-none-any.whl", hash = "sha256:bf141e2d380c8520eada8b351f4e319ffee9636328c137aa432bc486ca1200e1"}, - {file = "kubernetes-31.0.0.tar.gz", hash = "sha256:28945de906c8c259c1ebe62703b56a03b714049372196f854105afe4e6d014c0"}, + {file = "kubernetes-32.0.0-py2.py3-none-any.whl", hash = "sha256:60fd8c29e8e43d9c553ca4811895a687426717deba9c0a66fb2dcc3f5ef96692"}, + {file = "kubernetes-32.0.0.tar.gz", hash = "sha256:319fa840345a482001ac5d6062222daeb66ec4d1bcb3087402aed685adf0aecb"}, ] [package.dependencies] @@ -4308,6 +4310,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"}, @@ -4316,6 +4319,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"}, @@ -4324,6 +4328,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"}, @@ -4332,6 +4337,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"}, @@ -4340,6 +4346,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"}, {file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"}, @@ -5083,4 +5090,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.13" -content-hash = "d4d817d0a3690b7a39222bf79420e3adb764b47c6c1d2ee0c73a7599333114b1" +content-hash = "7511c2a5abad01f351b1d222d73a6b2c5374f386e8c035cce79934daca7ea762" diff --git a/pyproject.toml b/pyproject.toml index 1fb1ab82a2..b9cb7f8b1f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,7 +58,7 @@ detect-secrets = "1.5.0" google-api-python-client = "2.160.0" google-auth-httplib2 = ">=0.1,<0.3" jsonschema = "4.23.0" -kubernetes = "31.0.0" +kubernetes = "32.0.0" microsoft-kiota-abstractions = "1.9.1" msgraph-sdk = "1.18.0" numpy = "2.0.2" From 43c13b7ba14d39f67ca7a5df4522ed6d756e4984 Mon Sep 17 00:00:00 2001 From: Mario Rodriguez Lopez <101330800+MarioRgzLpz@users.noreply.github.com> Date: Tue, 4 Feb 2025 23:24:11 +0100 Subject: [PATCH 30/31] feat(microsoft365): add new check `admincenter_settings_password_never_expire` (#6023) Co-authored-by: MrCloudSec --- .../{microsoft365_regions.py => regions.py} | 0 .../microsoft365/microsoft365_provider.py | 4 +- .../admincenter/admincenter_service.py | 29 +++++ .../__init__.py | 0 ...ttings_password_never_expire.metadata.json | 30 +++++ ...incenter_settings_password_never_expire.py | 27 +++++ ...sers_between_two_and_four_global_admins.py | 4 +- .../lib/regions/microsoft365_regions_test.py | 2 +- .../microsoft365/microsoft365_fixtures.py | 1 - .../microsoft365_provider_test.py | 2 - ...ter_settings_password_never_expire_test.py | 112 ++++++++++++++++++ 11 files changed, 201 insertions(+), 10 deletions(-) rename prowler/providers/microsoft365/lib/regions/{microsoft365_regions.py => regions.py} (100%) create mode 100644 prowler/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/__init__.py create mode 100644 prowler/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire.metadata.json create mode 100644 prowler/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire.py create mode 100644 tests/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire_test.py diff --git a/prowler/providers/microsoft365/lib/regions/microsoft365_regions.py b/prowler/providers/microsoft365/lib/regions/regions.py similarity index 100% rename from prowler/providers/microsoft365/lib/regions/microsoft365_regions.py rename to prowler/providers/microsoft365/lib/regions/regions.py diff --git a/prowler/providers/microsoft365/microsoft365_provider.py b/prowler/providers/microsoft365/microsoft365_provider.py index dff8da4d97..af7a723f67 100644 --- a/prowler/providers/microsoft365/microsoft365_provider.py +++ b/prowler/providers/microsoft365/microsoft365_provider.py @@ -50,9 +50,7 @@ from prowler.providers.microsoft365.exceptions.exceptions import ( Microsoft365TenantIdAndClientSecretNotBelongingToClientIdError, ) from prowler.providers.microsoft365.lib.mutelist.mutelist import Microsoft365Mutelist -from prowler.providers.microsoft365.lib.regions.microsoft365_regions import ( - get_regions_config, -) +from prowler.providers.microsoft365.lib.regions.regions import get_regions_config from prowler.providers.microsoft365.models import ( Microsoft365IdentityInfo, Microsoft365RegionConfig, diff --git a/prowler/providers/microsoft365/services/admincenter/admincenter_service.py b/prowler/providers/microsoft365/services/admincenter/admincenter_service.py index 2fc14d1ae6..fed450ca82 100644 --- a/prowler/providers/microsoft365/services/admincenter/admincenter_service.py +++ b/prowler/providers/microsoft365/services/admincenter/admincenter_service.py @@ -22,11 +22,13 @@ class AdminCenter(Microsoft365Service): gather( self._get_directory_roles(), self._get_groups(), + self._get_domains(), ) ) self.directory_roles = attributes[0] self.groups = attributes[1] + self.domains = attributes[2] async def _get_users(self): logger.info("Microsoft365 - Getting users...") @@ -130,6 +132,28 @@ class AdminCenter(Microsoft365Service): ) return groups + async def _get_domains(self): + logger.info("Microsoft365 - Getting domains...") + domains = {} + try: + domains_list = await self.client.domains.get() + domains.update({}) + for domain in domains_list.value: + domains.update( + { + domain.id: Domain( + id=domain.id, + password_validity_period=domain.password_validity_period_in_days, + ) + } + ) + + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + return domains + class User(BaseModel): id: str @@ -149,3 +173,8 @@ class Group(BaseModel): id: str name: str visibility: str + + +class Domain(BaseModel): + id: str + password_validity_period: int diff --git a/prowler/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/__init__.py b/prowler/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire.metadata.json b/prowler/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire.metadata.json new file mode 100644 index 0000000000..13528b0008 --- /dev/null +++ b/prowler/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "microsoft", + "CheckID": "admincenter_settings_password_never_expire", + "CheckTitle": "Ensure the 'Password expiration policy' is set to 'Set passwords to never expire (recommended)'", + "CheckType": [], + "ServiceName": "admincenter", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "medium", + "ResourceType": "Microsoft365Domain", + "Description": "This control ensures that the password expiration policy is set to 'Set passwords to never expire (recommended)'. This aligns with modern recommendations to enhance security by avoiding arbitrary password changes and focusing on supplementary controls like MFA.", + "Risk": "Arbitrary password expiration policies can lead to weaker passwords due to frequent changes. Users may adopt insecure habits such as using simple, memorable passwords.", + "RelatedUrl": "https://www.cisecurity.org/insights/white-papers/cis-password-policy-guide", + "Remediation": { + "Code": { + "CLI": "Set-MsolUser -UserPrincipalName -PasswordNeverExpires $true", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enable the 'Never Expire Passwords' option in Microsoft 365 Admin Center.", + "Url": "https://learn.microsoft.com/en-us/microsoft-365/admin/misc/password-policy-recommendations?view=o365-worldwide" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire.py b/prowler/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire.py new file mode 100644 index 0000000000..8de62dd8d5 --- /dev/null +++ b/prowler/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire.py @@ -0,0 +1,27 @@ +from prowler.lib.check.models import Check, Check_Report_Microsoft365 +from prowler.providers.microsoft365.services.admincenter.admincenter_client import ( + admincenter_client, +) + + +class admincenter_settings_password_never_expire(Check): + def execute(self) -> Check_Report_Microsoft365: + findings = [] + for domain in admincenter_client.domains.values(): + report = Check_Report_Microsoft365(self.metadata(), resource=domain) + report.resource_id = domain.id + report.resource_name = domain.id + report.status = "FAIL" + report.status_extended = ( + f"Domain {domain.id} does not have a Password never expires policy." + ) + + if domain.password_validity_period == 2147483647: + report.status = "PASS" + report.status_extended = ( + f"Domain {domain.id} Password policy is set to never expire." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/microsoft365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins.py b/prowler/providers/microsoft365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins.py index aa0fc978c0..6002e67e4c 100644 --- a/prowler/providers/microsoft365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins.py +++ b/prowler/providers/microsoft365/services/admincenter/admincenter_users_between_two_and_four_global_admins/admincenter_users_between_two_and_four_global_admins.py @@ -9,9 +9,7 @@ class admincenter_users_between_two_and_four_global_admins(Check): findings = [] directory_roles = admincenter_client.directory_roles - report = Check_Report_Microsoft365( - metadata=self.metadata(), resource=admincenter_client.directory_roles - ) + report = Check_Report_Microsoft365(metadata=self.metadata(), resource={}) report.status = "FAIL" report.resource_name = "Global Administrator" diff --git a/tests/providers/microsoft365/lib/regions/microsoft365_regions_test.py b/tests/providers/microsoft365/lib/regions/microsoft365_regions_test.py index 3e358511b8..49ec5a480d 100644 --- a/tests/providers/microsoft365/lib/regions/microsoft365_regions_test.py +++ b/tests/providers/microsoft365/lib/regions/microsoft365_regions_test.py @@ -1,6 +1,6 @@ from azure.identity import AzureAuthorityHosts -from prowler.providers.microsoft365.lib.regions.microsoft365_regions import ( +from prowler.providers.microsoft365.lib.regions.regions import ( MICROSOFT365_CHINA_CLOUD, MICROSOFT365_GENERIC_CLOUD, MICROSOFT365_US_GOV_CLOUD, diff --git a/tests/providers/microsoft365/microsoft365_fixtures.py b/tests/providers/microsoft365/microsoft365_fixtures.py index 79e8ec118f..1dbb09c612 100644 --- a/tests/providers/microsoft365/microsoft365_fixtures.py +++ b/tests/providers/microsoft365/microsoft365_fixtures.py @@ -29,7 +29,6 @@ def set_mocked_microsoft365_provider( audit_config: dict = None, azure_region_config: Microsoft365RegionConfig = Microsoft365RegionConfig(), ) -> Microsoft365Provider: - provider = MagicMock() provider.type = "microsoft365" provider.session.credentials = credentials diff --git a/tests/providers/microsoft365/microsoft365_provider_test.py b/tests/providers/microsoft365/microsoft365_provider_test.py index b475a02ca6..7658301e44 100644 --- a/tests/providers/microsoft365/microsoft365_provider_test.py +++ b/tests/providers/microsoft365/microsoft365_provider_test.py @@ -212,7 +212,6 @@ class TestMicrosoft365Provider: "prowler.providers.microsoft365.microsoft365_provider.GraphServiceClient" ) as mock_graph_client, ): - # Mock the return value of DefaultAzureCredential mock_credentials = MagicMock() mock_credentials.get_token.return_value = AccessToken( @@ -271,7 +270,6 @@ class TestMicrosoft365Provider: with patch( "prowler.providers.microsoft365.microsoft365_provider.Microsoft365Provider.setup_session" ) as mock_setup_session: - mock_setup_session.side_effect = Microsoft365HTTPResponseError( file="test_file", original_exception="Simulated HttpResponseError" ) diff --git a/tests/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire_test.py b/tests/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire_test.py new file mode 100644 index 0000000000..b49bc98095 --- /dev/null +++ b/tests/providers/microsoft365/services/admincenter/admincenter_settings_password_never_expire/admincenter_settings_password_never_expire_test.py @@ -0,0 +1,112 @@ +from unittest import mock +from uuid import uuid4 + +from tests.providers.microsoft365.microsoft365_fixtures import ( + DOMAIN, + set_mocked_microsoft365_provider, +) + + +class Test_admincenter_settings_password_never_expire: + def test_admincenter_no_domains(self): + admincenter_client = mock.MagicMock + admincenter_client.audited_tenant = "audited_tenant" + admincenter_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_microsoft365_provider(), + ), + mock.patch( + "prowler.providers.microsoft365.services.admincenter.admincenter_settings_password_never_expire.admincenter_settings_password_never_expire.admincenter_client", + new=admincenter_client, + ), + ): + from prowler.providers.microsoft365.services.admincenter.admincenter_settings_password_never_expire.admincenter_settings_password_never_expire import ( + admincenter_settings_password_never_expire, + ) + + admincenter_client.domains = {} + + check = admincenter_settings_password_never_expire() + result = check.execute() + assert len(result) == 0 + + def test_admincenter_domain_password_expire(self): + admincenter_client = mock.MagicMock + admincenter_client.audited_tenant = "audited_tenant" + admincenter_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_microsoft365_provider(), + ), + mock.patch( + "prowler.providers.microsoft365.services.admincenter.admincenter_settings_password_never_expire.admincenter_settings_password_never_expire.admincenter_client", + new=admincenter_client, + ), + ): + from prowler.providers.microsoft365.services.admincenter.admincenter_service import ( + Domain, + ) + from prowler.providers.microsoft365.services.admincenter.admincenter_settings_password_never_expire.admincenter_settings_password_never_expire import ( + admincenter_settings_password_never_expire, + ) + + id_domain = str(uuid4()) + + admincenter_client.domains = { + id_domain: Domain(id=id_domain, password_validity_period=5), + } + + check = admincenter_settings_password_never_expire() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Domain {id_domain} does not have a Password never expires policy." + ) + assert result[0].resource_name == id_domain + assert result[0].resource_id == id_domain + + def test_admincenter_password_not_expire(self): + admincenter_client = mock.MagicMock + admincenter_client.audited_tenant = "audited_tenant" + admincenter_client.audited_domain = DOMAIN + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_microsoft365_provider(), + ), + mock.patch( + "prowler.providers.microsoft365.services.admincenter.admincenter_settings_password_never_expire.admincenter_settings_password_never_expire.admincenter_client", + new=admincenter_client, + ), + ): + from prowler.providers.microsoft365.services.admincenter.admincenter_service import ( + Domain, + ) + from prowler.providers.microsoft365.services.admincenter.admincenter_settings_password_never_expire.admincenter_settings_password_never_expire import ( + admincenter_settings_password_never_expire, + ) + + id_domain = str(uuid4()) + + admincenter_client.domains = { + id_domain: Domain(id=id_domain, password_validity_period=2147483647), + } + + check = admincenter_settings_password_never_expire() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Domain {id_domain} Password policy is set to never expire." + ) + assert result[0].resource_name == id_domain + assert result[0].resource_id == id_domain From f3b121950d5d03e028158cb8080069802dfba260 Mon Sep 17 00:00:00 2001 From: Mario Rodriguez Lopez <101330800+MarioRgzLpz@users.noreply.github.com> Date: Wed, 5 Feb 2025 01:47:14 +0100 Subject: [PATCH 31/31] feat(entra): add new `entra` service for Microsoft365 (#6326) Co-authored-by: MrCloudSec --- .../microsoft365/services/entra/__init__.py | 0 .../services/entra/entra_client.py | 4 + .../services/entra/entra_service.py | 105 ++++++++++++++++++ .../entra/microsoft365_entra_service_test.py | 45 ++++++++ 4 files changed, 154 insertions(+) create mode 100644 prowler/providers/microsoft365/services/entra/__init__.py create mode 100644 prowler/providers/microsoft365/services/entra/entra_client.py create mode 100644 prowler/providers/microsoft365/services/entra/entra_service.py create mode 100644 tests/providers/microsoft365/services/entra/microsoft365_entra_service_test.py diff --git a/prowler/providers/microsoft365/services/entra/__init__.py b/prowler/providers/microsoft365/services/entra/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/microsoft365/services/entra/entra_client.py b/prowler/providers/microsoft365/services/entra/entra_client.py new file mode 100644 index 0000000000..1a3b921adf --- /dev/null +++ b/prowler/providers/microsoft365/services/entra/entra_client.py @@ -0,0 +1,4 @@ +from prowler.providers.common.provider import Provider +from prowler.providers.microsoft365.services.entra.entra_service import Entra + +entra_client = Entra(Provider.get_global_provider()) diff --git a/prowler/providers/microsoft365/services/entra/entra_service.py b/prowler/providers/microsoft365/services/entra/entra_service.py new file mode 100644 index 0000000000..8561a31edd --- /dev/null +++ b/prowler/providers/microsoft365/services/entra/entra_service.py @@ -0,0 +1,105 @@ +from asyncio import gather, get_event_loop +from typing import List, Optional + +from pydantic import BaseModel + +from prowler.lib.logger import logger +from prowler.providers.microsoft365.lib.service.service import Microsoft365Service +from prowler.providers.microsoft365.microsoft365_provider import Microsoft365Provider + + +class Entra(Microsoft365Service): + def __init__(self, provider: Microsoft365Provider): + super().__init__(provider) + + loop = get_event_loop() + + attributes = loop.run_until_complete( + gather( + self._get_authorization_policy(), + ) + ) + + self.authorization_policy = attributes[0] + + async def _get_authorization_policy(self): + logger.info("Entra - Getting authorization policy...") + + authorization_policy = {} + try: + auth_policy = await self.client.policies.authorization_policy.get() + + default_user_role_permissions = getattr( + auth_policy, "default_user_role_permissions", None + ) + + authorization_policy.update( + { + auth_policy.id: AuthorizationPolicy( + id=auth_policy.id, + name=auth_policy.display_name, + description=auth_policy.description, + default_user_role_permissions=DefaultUserRolePermissions( + allowed_to_create_apps=getattr( + default_user_role_permissions, + "allowed_to_create_apps", + None, + ), + allowed_to_create_security_groups=getattr( + default_user_role_permissions, + "allowed_to_create_security_groups", + None, + ), + allowed_to_create_tenants=getattr( + default_user_role_permissions, + "allowed_to_create_tenants", + None, + ), + allowed_to_read_bitlocker_keys_for_owned_device=getattr( + default_user_role_permissions, + "allowed_to_read_bitlocker_keys_for_owned_device", + None, + ), + allowed_to_read_other_users=getattr( + default_user_role_permissions, + "allowed_to_read_other_users", + None, + ), + odata_type=getattr( + default_user_role_permissions, "odata_type", None + ), + permission_grant_policies_assigned=[ + policy_assigned + for policy_assigned in getattr( + default_user_role_permissions, + "permission_grant_policies_assigned", + [], + ) + ], + ), + ) + } + ) + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + return authorization_policy + + +class DefaultUserRolePermissions(BaseModel): + allowed_to_create_apps: Optional[bool] + allowed_to_create_security_groups: Optional[bool] + allowed_to_create_tenants: Optional[bool] + allowed_to_read_bitlocker_keys_for_owned_device: Optional[bool] + allowed_to_read_other_users: Optional[bool] + odata_type: Optional[str] + permission_grant_policies_assigned: Optional[List[str]] = None + + +class AuthorizationPolicy(BaseModel): + id: str + name: str + description: str + default_user_role_permissions: Optional[DefaultUserRolePermissions] diff --git a/tests/providers/microsoft365/services/entra/microsoft365_entra_service_test.py b/tests/providers/microsoft365/services/entra/microsoft365_entra_service_test.py new file mode 100644 index 0000000000..86109478e2 --- /dev/null +++ b/tests/providers/microsoft365/services/entra/microsoft365_entra_service_test.py @@ -0,0 +1,45 @@ +from unittest.mock import patch + +from prowler.providers.microsoft365.models import Microsoft365IdentityInfo +from prowler.providers.microsoft365.services.entra.entra_service import ( + AuthorizationPolicy, + Entra, +) +from tests.providers.microsoft365.microsoft365_fixtures import ( + DOMAIN, + set_mocked_microsoft365_provider, +) + + +async def mock_entra_get_authorization_policy(_): + return { + "id-1": AuthorizationPolicy( + id="id-1", + name="Name 1", + description="Description 1", + default_user_role_permissions=None, + ) + } + + +@patch( + "prowler.providers.microsoft365.services.entra.entra_service.Entra._get_authorization_policy", + new=mock_entra_get_authorization_policy, +) +class Test_Entra_Service: + def test_get_client(self): + admincenter_client = Entra( + set_mocked_microsoft365_provider( + identity=Microsoft365IdentityInfo(tenant_domain=DOMAIN) + ) + ) + assert admincenter_client.client.__class__.__name__ == "GraphServiceClient" + + def test_get_authorization_policy(self): + entra_client = Entra(set_mocked_microsoft365_provider()) + assert entra_client.authorization_policy["id-1"].id == "id-1" + assert entra_client.authorization_policy["id-1"].name == "Name 1" + assert entra_client.authorization_policy["id-1"].description == "Description 1" + assert not entra_client.authorization_policy[ + "id-1" + ].default_user_role_permissions