Compare commits

...

19 Commits

Author SHA1 Message Date
Adrián Jesús Peña Rodríguez
75a429740e fix: set lxml version (#8253)
(cherry picked from commit 8bcec4926b)

# Conflicts:
#	api/CHANGELOG.md
#	api/poetry.lock
#	api/pyproject.toml
2025-07-11 09:44:27 +00:00
Prowler Bot
e7f5ad1bf2 fix(docs): GitHub provider mkdocs and -h (#8252)
Co-authored-by: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com>
2025-07-11 17:11:08 +08:00
Prowler Bot
e3d7fe4436 chore(release): Bump version to v5.8.2 (#8239)
Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
2025-07-10 19:14:55 +08:00
Prowler Bot
3bd5f4f650 feat(exceptions): add custom error for provider connection during scans (#8237)
Co-authored-by: Víctor Fernández Poyatos <victor@prowler.com>
2025-07-10 11:14:14 +02:00
Prowler Bot
30dd1df2ae fix: changelog entries with new specification (#8238)
Co-authored-by: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com>
2025-07-10 14:43:28 +05:45
Prowler Bot
d338123c62 fix(azure/storage): use BaseModel for all Storage models (#8227)
Co-authored-by: Rubén De la Torre Vico <ruben@prowler.com>
Co-authored-by: Sergio Garcia <hello@mistercloudsec.com>
2025-07-09 17:23:08 +08:00
Prowler Bot
ff4a38a985 fix(firehose): list all streams and fix firehose_stream_encrypted_at_rest logic (#8226)
Co-authored-by: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com>
2025-07-09 16:31:17 +08:00
Prowler Bot
db23a1be5f fix(overview): use findings latest to get new (#8220)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2025-07-08 19:37:41 +05:45
Prowler Bot
0f62ec3e07 fix(api): make invitation email comparison case-insensitive (#8217)
Co-authored-by: Pablo Lara <larabjj@gmail.com>
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2025-07-08 15:01:37 +02:00
Prowler Bot
609137521b fix: Remove type validation while updating provider credentials (#8218)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
Co-authored-by: Víctor Fernández Poyatos <victor@prowler.com>
2025-07-08 18:08:40 +05:45
Prowler Bot
f141b8e87f feat(tasks): create overview queue for summaries and overviews (#8216)
Co-authored-by: Víctor Fernández Poyatos <victor@prowler.com>
2025-07-08 14:11:04 +02:00
Prowler Bot
ad3aa2a2cc fix(findings): avoid backfill on empty scans (#8188)
Co-authored-by: Víctor Fernández Poyatos <victor@prowler.com>
2025-07-07 16:57:14 +02:00
Prowler Bot
1e4ac36de9 fix(ec2): allow empty values for http_endpoint in templates (#8187)
Co-authored-by: Pedro Martín <pedromarting3@gmail.com>
2025-07-04 19:05:33 +08:00
Prowler Bot
2fa8d7c351 fix(iam): detect wildcarded ARNs in sts:AssumeRole policy resources (#8185)
Co-authored-by: Kay Agahd <kagahd@users.noreply.github.com>
Co-authored-by: Sergio Garcia <hello@mistercloudsec.com>
2025-07-04 16:48:28 +08:00
Prowler Bot
39306e47f2 chore(release): Bump version to v5.8.1 (#8179)
Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
2025-07-03 23:10:21 +08:00
Pablo Lara
084b33c5dd fix: disable dynamic filters for now (#8177) 2025-07-03 14:24:21 +02:00
Alejandro Bailo
06ce25d6a2 fix: remove duplicated calls during promise all resolving (#8176) 2025-07-03 14:24:13 +02:00
Pablo Lara
061cad9bda fix: bug when updating credentials for m365 (#8173) 2025-07-03 11:35:12 +02:00
César Arroba
7d4541a1be chore(api): prowler version 2025-07-03 09:48:00 +02:00
62 changed files with 2956 additions and 769 deletions

View File

@@ -136,12 +136,6 @@ jobs:
run: |
poetry check --lock
- name: Prevents known compatibility error between lxml and libxml2/libxmlsec versions - https://github.com/xmlsec/python-xmlsec/issues/320
working-directory: ./api
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
run: |
poetry run pip install --force-reinstall --no-binary lxml lxml
- name: Lint with ruff
working-directory: ./api
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'

View File

@@ -4,6 +4,42 @@ All notable changes to the **Prowler API** are documented in this file.
## [v1.10.0] (Prowler UNRELEASED)
<<<<<<< HEAD
=======
### Added
- SSO with SAML support [(#8175)](https://github.com/prowler-cloud/prowler/pull/8175)
- `/processors` endpoints to post-process findings. Currently, only the Mutelist processor is supported to allow to mute findings.
### Security
- Enhanced password validation to enforce 12+ character passwords with special characters, uppercase, lowercase, and numbers [(#8225)](https://github.com/prowler-cloud/prowler/pull/8225)
---
## [v1.9.2] (Prowler v5.8.2)
### Fixed
- Pinned lxml and xmlsec versions to avoid compatibility issues [(#8253)](https://github.com/prowler-cloud/prowler/pull/8253)
>>>>>>> 8bcec4926 (fix: set lxml version (#8253))
---
## [v1.9.1] (Prowler v5.8.1)
### Added
- Custom exception for provider connection errors during scans [(#8234)](https://github.com/prowler-cloud/prowler/pull/8234)
### Changed
- Summary and overview tasks now use a dedicated queue and no longer propagate errors to compliance tasks [(#8214)](https://github.com/prowler-cloud/prowler/pull/8214)
### Fixed
- Scan with no resources will not trigger legacy code for findings metadata [(#8183)](https://github.com/prowler-cloud/prowler/pull/8183)
- Invitation email comparison case-insensitive [(#8206)](https://github.com/prowler-cloud/prowler/pull/8206)
### Removed
- Validation of the provider's secret type during updates [(#8197)](https://github.com/prowler-cloud/prowler/pull/8197)
---
## [v1.9.0] (Prowler v5.8.0)

View File

@@ -57,10 +57,6 @@ RUN poetry install --no-root && \
RUN poetry run python "$(poetry env info --path)/src/prowler/prowler/providers/m365/lib/powershell/m365_powershell.py"
# Prevents known compatibility error between lxml and libxml2/libxmlsec versions.
# See: https://github.com/xmlsec/python-xmlsec/issues/320
RUN poetry run pip install --force-reinstall --no-binary lxml lxml
COPY src/backend/ ./backend/
COPY docker-entrypoint.sh ./docker-entrypoint.sh

View File

@@ -32,7 +32,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,scan-reports,deletion,backfill -E --max-tasks-per-child 1
poetry run python -m celery -A config.celery worker -l "${DJANGO_LOGGING_LEVEL:-info}" -Q celery,scans,scan-reports,deletion,backfill,overview -E --max-tasks-per-child 1
}
start_worker_beat() {

1787
api/poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -23,12 +23,18 @@ dependencies = [
"drf-spectacular==0.27.2",
"drf-spectacular-jsonapi==0.5.1",
"gunicorn==23.0.0",
<<<<<<< HEAD
"prowler @ git+https://github.com/prowler-cloud/prowler.git@v5.8",
=======
"lxml==5.3.2",
"prowler @ git+https://github.com/prowler-cloud/prowler.git@master",
>>>>>>> 8bcec4926 (fix: set lxml version (#8253))
"psycopg2-binary==2.9.9",
"pytest-celery[redis] (>=1.0.1,<2.0.0)",
"sentry-sdk[django] (>=2.20.0,<3.0.0)",
"uuid6==2024.7.10",
"openai (>=1.82.0,<2.0.0)"
"openai (>=1.82.0,<2.0.0)",
"xmlsec==1.3.14"
]
description = "Prowler's API (Django/DRF)"
license = "Apache-2.0"
@@ -36,7 +42,7 @@ name = "prowler-api"
package-mode = false
# Needed for the SDK compatibility
requires-python = ">=3.11,<3.13"
version = "1.9.0"
version = "1.9.1"
[project.scripts]
celery = "src.backend.config.settings.celery"

View File

@@ -57,6 +57,11 @@ class TaskInProgressException(TaskManagementError):
super().__init__()
# Provider connection errors
class ProviderConnectionError(Exception):
"""Base exception for provider connection errors."""
def custom_exception_handler(exc, context):
if isinstance(exc, django_validation_error):
if hasattr(exc, "error_dict"):

View File

@@ -936,6 +936,11 @@ class Invitation(RowLevelSecurityProtectedModel):
null=True,
)
def save(self, *args, **kwargs):
if self.email:
self.email = self.email.strip().lower()
super().save(*args, **kwargs)
class Meta(RowLevelSecurityProtectedModel.Meta):
db_table = "invitations"

View File

@@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: Prowler API
version: 1.9.0
version: 1.9.1
description: |-
Prowler API specification.
@@ -9661,7 +9661,6 @@ components:
* `static` - Key-value pairs
* `role` - Role assumption
* `service_account` - GCP Service Account Key
readOnly: true
secret:
oneOf:
- type: object
@@ -11373,7 +11372,6 @@ components:
* `static` - Key-value pairs
* `role` - Role assumption
* `service_account` - GCP Service Account Key
readOnly: true
secret:
oneOf:
- type: object

View File

@@ -254,7 +254,7 @@ class TestValidateInvitation:
assert result == invitation
mock_db.get.assert_called_once_with(
token="VALID_TOKEN", email="user@example.com"
token="VALID_TOKEN", email__iexact="user@example.com"
)
def test_invitation_not_found_raises_validation_error(self):
@@ -269,7 +269,7 @@ class TestValidateInvitation:
"invitation_token": "Invalid invitation code."
}
mock_db.get.assert_called_once_with(
token="INVALID_TOKEN", email="user@example.com"
token="INVALID_TOKEN", email__iexact="user@example.com"
)
def test_invitation_not_found_raises_not_found(self):
@@ -284,7 +284,7 @@ class TestValidateInvitation:
assert exc_info.value.detail == "Invitation is not valid."
mock_db.get.assert_called_once_with(
token="INVALID_TOKEN", email="user@example.com"
token="INVALID_TOKEN", email__iexact="user@example.com"
)
def test_invitation_expired(self, invitation):
@@ -332,5 +332,27 @@ class TestValidateInvitation:
"invitation_token": "Invalid invitation code."
}
mock_db.get.assert_called_once_with(
token="VALID_TOKEN", email="different@example.com"
token="VALID_TOKEN", email__iexact="different@example.com"
)
def test_valid_invitation_uppercase_email(self):
"""Test that validate_invitation works with case-insensitive email lookup."""
uppercase_email = "USER@example.com"
invitation = MagicMock(spec=Invitation)
invitation.token = "VALID_TOKEN"
invitation.email = uppercase_email
invitation.expires_at = datetime.now(timezone.utc) + timedelta(days=1)
invitation.state = Invitation.State.PENDING
invitation.tenant = MagicMock()
with patch("api.utils.Invitation.objects.using") as mock_using:
mock_db = mock_using.return_value
mock_db.get.return_value = invitation
result = validate_invitation("VALID_TOKEN", "user@example.com")
assert result == invitation
mock_db.get.assert_called_once_with(
token="VALID_TOKEN", email__iexact="user@example.com"
)

View File

@@ -2029,6 +2029,104 @@ class TestProviderSecretViewSet:
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
def test_provider_secrets_partial_update_with_secret_type(
self, authenticated_client, provider_secret_fixture
):
provider_secret, *_ = provider_secret_fixture
data = {
"data": {
"type": "provider-secrets",
"id": str(provider_secret.id),
"attributes": {
"name": "new_name",
"secret": {
"service_account_key": {},
},
"secret_type": "service_account",
},
"relationships": {
"provider": {
"data": {
"type": "providers",
"id": str(provider_secret.provider.id),
}
}
},
}
}
response = authenticated_client.patch(
reverse("providersecret-detail", kwargs={"pk": provider_secret.id}),
data=json.dumps(data),
content_type="application/vnd.api+json",
)
assert response.status_code == status.HTTP_200_OK
provider_secret.refresh_from_db()
assert provider_secret.name == "new_name"
assert provider_secret.secret == {"service_account_key": {}}
def test_provider_secrets_partial_update_with_invalid_secret_type(
self, authenticated_client, provider_secret_fixture
):
provider_secret, *_ = provider_secret_fixture
data = {
"data": {
"type": "provider-secrets",
"id": str(provider_secret.id),
"attributes": {
"name": "new_name",
"secret": {
"service_account_key": {},
},
"secret_type": "static",
},
"relationships": {
"provider": {
"data": {
"type": "providers",
"id": str(provider_secret.provider.id),
}
}
},
}
}
response = authenticated_client.patch(
reverse("providersecret-detail", kwargs={"pk": provider_secret.id}),
data=json.dumps(data),
content_type="application/vnd.api+json",
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
def test_provider_secrets_partial_update_without_secret_type_but_different(
self, authenticated_client, provider_secret_fixture
):
provider_secret, *_ = provider_secret_fixture
data = {
"data": {
"type": "provider-secrets",
"id": str(provider_secret.id),
"attributes": {
"name": "new_name",
"secret": {
"service_account_key": {},
},
},
"relationships": {
"provider": {
"data": {
"type": "providers",
"id": str(provider_secret.provider.id),
}
}
},
}
}
response = authenticated_client.patch(
reverse("providersecret-detail", kwargs={"pk": provider_secret.id}),
data=json.dumps(data),
content_type="application/vnd.api+json",
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
@pytest.mark.django_db
class TestScanViewSet:
@@ -3371,6 +3469,61 @@ class TestFindingViewSet:
]
}
def test_findings_metadata_backfill(
self, authenticated_client, scans_fixture, findings_fixture
):
scan = scans_fixture[0]
scan.unique_resource_count = 1
scan.save()
with patch(
"api.v1.views.backfill_scan_resource_summaries_task.apply_async"
) as mock_backfill_task:
response = authenticated_client.get(
reverse("finding-metadata"),
{"filter[scan]": str(scan.id)},
)
assert response.status_code == status.HTTP_200_OK
mock_backfill_task.assert_called()
def test_findings_metadata_backfill_no_resources(
self, authenticated_client, scans_fixture
):
scan_id = str(scans_fixture[0].id)
with patch(
"api.v1.views.backfill_scan_resource_summaries_task.apply_async"
) as mock_backfill_task:
response = authenticated_client.get(
reverse("finding-metadata"),
{"filter[scan]": scan_id},
)
assert response.status_code == status.HTTP_200_OK
mock_backfill_task.assert_not_called()
def test_findings_metadata_latest_backfill(
self, authenticated_client, scans_fixture, findings_fixture
):
scan = scans_fixture[0]
scan.unique_resource_count = 1
scan.save()
with patch(
"api.v1.views.backfill_scan_resource_summaries_task.apply_async"
) as mock_backfill_task:
response = authenticated_client.get(reverse("finding-metadata_latest"))
assert response.status_code == status.HTTP_200_OK
mock_backfill_task.assert_called()
def test_findings_metadata_latest_backfill_no_resources(
self, authenticated_client, scans_fixture
):
with patch(
"api.v1.views.backfill_scan_resource_summaries_task.apply_async"
) as mock_backfill_task:
response = authenticated_client.get(reverse("finding-metadata_latest"))
assert response.status_code == status.HTTP_200_OK
mock_backfill_task.assert_not_called()
def test_findings_latest(self, authenticated_client, latest_scan_finding):
response = authenticated_client.get(
reverse("finding-latest"),

View File

@@ -187,7 +187,7 @@ def validate_invitation(
# Admin DB connector is used to bypass RLS protection since the invitation belongs to a tenant the user
# is not a member of yet
invitation = Invitation.objects.using(MainRouter.admin_db).get(
token=invitation_token, email=email
token=invitation_token, email__iexact=email
)
except Invitation.DoesNotExist:
if raise_not_found:

View File

@@ -1308,12 +1308,13 @@ class ProviderSecretUpdateSerializer(BaseWriteProviderSecretSerializer):
"inserted_at": {"read_only": True},
"updated_at": {"read_only": True},
"provider": {"read_only": True},
"secret_type": {"read_only": True},
"secret_type": {"required": False},
}
def validate(self, attrs):
provider = self.instance.provider
secret_type = self.instance.secret_type
# To allow updating a secret with the same type without making the `secret_type` mandatory
secret_type = attrs.get("secret_type") or self.instance.secret_type
secret = attrs.get("secret")
validated_attrs = super().validate(attrs)

View File

@@ -271,7 +271,7 @@ class SchemaView(SpectacularAPIView):
def get(self, request, *args, **kwargs):
spectacular_settings.TITLE = "Prowler API"
spectacular_settings.VERSION = "1.9.0"
spectacular_settings.VERSION = "1.9.1"
spectacular_settings.DESCRIPTION = (
"Prowler API specification.\n\nThis file is auto-generated."
)
@@ -2127,9 +2127,12 @@ class FindingViewSet(PaginateByPkMixin, BaseRLSViewSet):
# ToRemove: Temporary fallback mechanism
if not queryset.exists():
scan_ids = Scan.objects.filter(
raw_scans_ids = Scan.objects.filter(
tenant_id=tenant_id, **scan_based_filters
).values_list("id", flat=True)
).values_list("id", "unique_resource_count")
scan_ids = [
scan_id for scan_id, count in raw_scans_ids if count and count > 0
]
for scan_id in scan_ids:
backfill_scan_resource_summaries_task.apply_async(
kwargs={"tenant_id": tenant_id, "scan_id": scan_id}
@@ -2215,7 +2218,12 @@ class FindingViewSet(PaginateByPkMixin, BaseRLSViewSet):
.order_by("provider_id", "-inserted_at")
.distinct("provider_id")
)
latest_scans_ids = list(latest_scans_queryset.values_list("id", flat=True))
raw_latest_scans_ids = list(
latest_scans_queryset.values_list("id", "unique_resource_count")
)
latest_scans_ids = [
scan_id for scan_id, count in raw_latest_scans_ids if count and count > 0
]
queryset = ResourceScanSummary.objects.filter(
tenant_id=tenant_id,

View File

@@ -4,6 +4,7 @@ from config.env import env
IGNORED_EXCEPTIONS = [
# Provider is not connected due to credentials errors
"is not connected",
"ProviderConnectionError",
# Authentication Errors from AWS
"InvalidToken",
"AccessDeniedException",
@@ -16,7 +17,7 @@ IGNORED_EXCEPTIONS = [
"InternalServerErrorException",
"AccessDenied",
"No Shodan API Key", # Shodan Check
"RequestLimitExceeded", # For now we don't want to log the RequestLimitExceeded errors
"RequestLimitExceeded", # For now, we don't want to log the RequestLimitExceeded errors
"ThrottlingException",
"Rate exceeded",
"SubscriptionRequiredException",
@@ -42,7 +43,9 @@ IGNORED_EXCEPTIONS = [
"AWSAccessKeyIDInvalidError",
"AWSSessionTokenExpiredError",
"EndpointConnectionError", # AWS Service is not available in a region
"Pool is closed", # The following comes from urllib3: eu-west-1 -- HTTPClientError[126]: An HTTP Client raised an unhandled exception: AWSHTTPSConnectionPool(host='hostname.s3.eu-west-1.amazonaws.com', port=443): Pool is closed.
# The following comes from urllib3: eu-west-1 -- HTTPClientError[126]: An HTTP Client raised an
# unhandled exception: AWSHTTPSConnectionPool(host='hostname.s3.eu-west-1.amazonaws.com', port=443): Pool is closed.
"Pool is closed",
# Authentication Errors from GCP
"ClientAuthenticationError",
"AuthorizationFailed",
@@ -71,7 +74,7 @@ IGNORED_EXCEPTIONS = [
def before_send(event, hint):
"""
before_send handles the Sentry events in order to sent them or not
before_send handles the Sentry events in order to send them or not
"""
# Ignore logs with the ignored_exceptions
# https://docs.python.org/3/library/logging.html#logrecord-objects

View File

@@ -14,6 +14,7 @@ from api.compliance import (
generate_scan_compliance,
)
from api.db_utils import create_objects_in_batches, rls_transaction
from api.exceptions import ProviderConnectionError
from api.models import (
ComplianceRequirementOverview,
Finding,
@@ -139,7 +140,7 @@ def perform_prowler_scan(
provider_instance.connected = True
except Exception as e:
provider_instance.connected = False
exc = ValueError(
exc = ProviderConnectionError(
f"Provider {provider_instance.provider} is not connected: {e}"
)
finally:

View File

@@ -37,6 +37,26 @@ from prowler.lib.outputs.finding import Finding as FindingOutput
logger = get_task_logger(__name__)
def _perform_scan_complete_tasks(tenant_id: str, scan_id: str, provider_id: str):
"""
Helper function to perform tasks after a scan is completed.
Args:
tenant_id (str): The tenant ID under which the scan was performed.
scan_id (str): The ID of the scan that was performed.
provider_id (str): The primary key of the Provider instance that was scanned.
"""
create_compliance_requirements_task.apply_async(
kwargs={"tenant_id": tenant_id, "scan_id": scan_id}
)
chain(
perform_scan_summary_task.si(tenant_id=tenant_id, scan_id=scan_id),
generate_outputs_task.si(
scan_id=scan_id, provider_id=provider_id, tenant_id=tenant_id
),
).apply_async()
@shared_task(base=RLSTask, name="provider-connection-check")
@set_tenant
def check_provider_connection_task(provider_id: str):
@@ -103,13 +123,7 @@ def perform_scan_task(
checks_to_execute=checks_to_execute,
)
chain(
perform_scan_summary_task.si(tenant_id, scan_id),
create_compliance_requirements_task.si(tenant_id=tenant_id, scan_id=scan_id),
generate_outputs.si(
scan_id=scan_id, provider_id=provider_id, tenant_id=tenant_id
),
).apply_async()
_perform_scan_complete_tasks(tenant_id, scan_id, provider_id)
return result
@@ -214,20 +228,12 @@ def perform_scheduled_scan_task(self, tenant_id: str, provider_id: str):
scheduler_task_id=periodic_task_instance.id,
)
chain(
perform_scan_summary_task.si(tenant_id, scan_instance.id),
create_compliance_requirements_task.si(
tenant_id=tenant_id, scan_id=str(scan_instance.id)
),
generate_outputs.si(
scan_id=str(scan_instance.id), provider_id=provider_id, tenant_id=tenant_id
),
).apply_async()
_perform_scan_complete_tasks(tenant_id, str(scan_instance.id), provider_id)
return result
@shared_task(name="scan-summary")
@shared_task(name="scan-summary", queue="overview")
def perform_scan_summary_task(tenant_id: str, scan_id: str):
return aggregate_findings(tenant_id=tenant_id, scan_id=scan_id)
@@ -243,7 +249,7 @@ def delete_tenant_task(tenant_id: str):
queue="scan-reports",
)
@set_tenant(keep_tenant=True)
def generate_outputs(scan_id: str, provider_id: str, tenant_id: str):
def generate_outputs_task(scan_id: str, provider_id: str, tenant_id: str):
"""
Process findings in batches and generate output files in multiple formats.
@@ -381,7 +387,7 @@ def backfill_scan_resource_summaries_task(tenant_id: str, scan_id: str):
return backfill_resource_scan_summaries(tenant_id=tenant_id, scan_id=scan_id)
@shared_task(base=RLSTask, name="scan-compliance-overviews")
@shared_task(base=RLSTask, name="scan-compliance-overviews", queue="overview")
def create_compliance_requirements_task(tenant_id: str, scan_id: str):
"""
Creates detailed compliance requirement records for a scan.

View File

@@ -12,6 +12,7 @@ from tasks.jobs.scan import (
)
from tasks.utils import CustomEncoder
from api.exceptions import ProviderConnectionError
from api.models import (
ComplianceRequirementOverview,
Finding,
@@ -203,7 +204,7 @@ class TestPerformScan:
provider_id = str(provider.id)
checks_to_execute = ["check1", "check2"]
with pytest.raises(ValueError):
with pytest.raises(ProviderConnectionError):
perform_prowler_scan(tenant_id, scan_id, provider_id, checks_to_execute)
scan.refresh_from_db()

View File

@@ -3,9 +3,10 @@ from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
from tasks.tasks import generate_outputs
from tasks.tasks import _perform_scan_complete_tasks, generate_outputs_task
# TODO Move this to outputs/reports jobs
@pytest.mark.django_db
class TestGenerateOutputs:
def setup_method(self):
@@ -17,7 +18,7 @@ class TestGenerateOutputs:
with patch("tasks.tasks.ScanSummary.objects.filter") as mock_filter:
mock_filter.return_value.exists.return_value = False
result = generate_outputs(
result = generate_outputs_task(
scan_id=self.scan_id,
provider_id=self.provider_id,
tenant_id=self.tenant_id,
@@ -99,7 +100,7 @@ class TestGenerateOutputs:
mock_compress.return_value = "/tmp/zipped.zip"
mock_upload.return_value = "s3://bucket/zipped.zip"
result = generate_outputs(
result = generate_outputs_task(
scan_id=self.scan_id,
provider_id=self.provider_id,
tenant_id=self.tenant_id,
@@ -150,7 +151,7 @@ class TestGenerateOutputs:
True,
]
result = generate_outputs(
result = generate_outputs_task(
scan_id="scan",
provider_id="provider",
tenant_id=self.tenant_id,
@@ -208,7 +209,7 @@ class TestGenerateOutputs:
{"aws": [(lambda x: True, MagicMock())]},
),
):
generate_outputs(
generate_outputs_task(
scan_id=self.scan_id,
provider_id=self.provider_id,
tenant_id=self.tenant_id,
@@ -276,7 +277,7 @@ class TestGenerateOutputs:
}
},
):
result = generate_outputs(
result = generate_outputs_task(
scan_id=self.scan_id,
provider_id=self.provider_id,
tenant_id=self.tenant_id,
@@ -346,7 +347,7 @@ class TestGenerateOutputs:
):
mock_summary.return_value.exists.return_value = True
result = generate_outputs(
result = generate_outputs_task(
scan_id=self.scan_id,
provider_id=self.provider_id,
tenant_id=self.tenant_id,
@@ -407,9 +408,31 @@ class TestGenerateOutputs:
),
):
with caplog.at_level("ERROR"):
generate_outputs(
generate_outputs_task(
scan_id=self.scan_id,
provider_id=self.provider_id,
tenant_id=self.tenant_id,
)
assert "Error deleting output files" in caplog.text
class TestScanCompleteTasks:
@patch("tasks.tasks.create_compliance_requirements_task.apply_async")
@patch("tasks.tasks.perform_scan_summary_task.si")
@patch("tasks.tasks.generate_outputs_task.si")
def test_scan_complete_tasks(
self, mock_outputs_task, mock_scan_summary_task, mock_compliance_tasks
):
_perform_scan_complete_tasks("tenant-id", "scan-id", "provider-id")
mock_compliance_tasks.assert_called_once_with(
kwargs={"tenant_id": "tenant-id", "scan_id": "scan-id"},
)
mock_scan_summary_task.assert_called_once_with(
scan_id="scan-id",
tenant_id="tenant-id",
)
mock_outputs_task.assert_called_once_with(
scan_id="scan-id",
provider_id="provider-id",
tenant_id="tenant-id",
)

View File

@@ -37,7 +37,7 @@ prowler github --github-app-id app_id --github-app-key app_key
If no login method is explicitly provided, Prowler will automatically attempt to authenticate using environment variables in the following order of precedence:
1. `GITHUB_PERSONAL_ACCESS_TOKEN`
2. `OAUTH_APP_TOKEN`
2. `GITHUB_OAUTH_APP_TOKEN`
3. `GITHUB_APP_ID` and `GITHUB_APP_KEY`
???+ note

View File

@@ -106,6 +106,8 @@ nav:
- Getting Started: tutorials/microsoft365/getting-started-m365.md
- Authentication: tutorials/microsoft365/authentication.md
- Use of PowerShell: tutorials/microsoft365/use-of-powershell.md
- GitHub:
- Authentication: tutorials/github/authentication.md
- IaC:
- Getting Started: tutorials/iac/getting-started-iac.md
- Developer Guide:

4
poetry.lock generated
View File

@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand.
# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand.
[[package]]
name = "about-time"
@@ -2678,6 +2678,8 @@ python-versions = "*"
groups = ["dev"]
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]

View File

@@ -9,6 +9,17 @@ All notable changes to the **Prowler SDK** are documented in this file.
### Changed
### Fixed
- Add GitHub provider to lateral panel in documentation and change -h environment variable output [(#8246)](https://github.com/prowler-cloud/prowler/pull/8246)
---
## [v5.8.1] (Prowler 5.8.1)
### Fixed
- Detect wildcarded ARNs in sts:AssumeRole policy resources [(#8164)](https://github.com/prowler-cloud/prowler/pull/8164)
- List all streams and `firehose_stream_encrypted_at_rest` logic [(#8213)](https://github.com/prowler-cloud/prowler/pull/8213)
- Allow empty values for http_endpoint in templates [(#8184)](https://github.com/prowler-cloud/prowler/pull/8184)
- Convert all Azure Storage models to Pydantic models to avoid serialization issues [(#8222)](https://github.com/prowler-cloud/prowler/pull/8222)
---

View File

@@ -12,7 +12,7 @@ from prowler.lib.logger import logger
timestamp = datetime.today()
timestamp_utc = datetime.now(timezone.utc).replace(tzinfo=timezone.utc)
prowler_version = "5.8.0"
prowler_version = "5.8.2"
html_logo_url = "https://github.com/prowler-cloud/prowler/"
square_logo_img = "https://prowler.com/wp-content/uploads/logo-html.png"
aws_logo = "https://user-images.githubusercontent.com/38561120/235953920-3e3fba08-0795-41dc-b480-9bea57db9f2e.png"

View File

@@ -18,7 +18,10 @@ class ec2_launch_template_imdsv2_required(Check):
and version.template_data.http_tokens == "required"
):
versions_with_imdsv2_required.append(str(version.version_number))
elif version.template_data.http_endpoint == "disabled":
elif (
version.template_data.http_endpoint == "disabled"
or not version.template_data.http_endpoint
):
versions_with_metadata_disabled.append(str(version.version_number))
else:
versions_with_no_imdsv2.append(str(version.version_number))

View File

@@ -25,18 +25,47 @@ class Firehose(AWSService):
def _list_delivery_streams(self, regional_client):
logger.info("Firehose - Listing delivery streams...")
try:
for stream_name in regional_client.list_delivery_streams()[
"DeliveryStreamNames"
]:
stream_arn = f"arn:{self.audited_partition}:firehose:{regional_client.region}:{self.audited_account}:deliverystream/{stream_name}"
if not self.audit_resources or (
is_resource_filtered(stream_arn, self.audit_resources)
):
self.delivery_streams[stream_arn] = DeliveryStream(
arn=stream_arn,
name=stream_name,
region=regional_client.region,
# Manual pagination using ExclusiveStartDeliveryStreamName
# This ensures we get all streams alphabetically without duplicates
exclusive_start_delivery_stream_name = None
processed_streams = set()
while True:
kwargs = {}
if exclusive_start_delivery_stream_name:
kwargs["ExclusiveStartDeliveryStreamName"] = (
exclusive_start_delivery_stream_name
)
response = regional_client.list_delivery_streams(**kwargs)
stream_names = response.get("DeliveryStreamNames", [])
for stream_name in stream_names:
if stream_name in processed_streams:
continue
processed_streams.add(stream_name)
stream_arn = f"arn:{self.audited_partition}:firehose:{regional_client.region}:{self.audited_account}:deliverystream/{stream_name}"
if not self.audit_resources or (
is_resource_filtered(stream_arn, self.audit_resources)
):
self.delivery_streams[stream_arn] = DeliveryStream(
arn=stream_arn,
name=stream_name,
region=regional_client.region,
)
if not response.get("HasMoreDeliveryStreams", False):
break
# Set the starting point for the next page (last stream name from current batch)
# ExclusiveStartDeliveryStreamName will start after this stream alphabetically
if stream_names:
exclusive_start_delivery_stream_name = stream_names[-1]
else:
break
except ClientError as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
@@ -61,13 +90,45 @@ class Firehose(AWSService):
describe_stream = self.regional_clients[
stream.region
].describe_delivery_stream(DeliveryStreamName=stream.name)
encryption_config = describe_stream.get(
"DeliveryStreamDescription", {}
).get("DeliveryStreamEncryptionConfiguration", {})
stream.kms_encryption = EncryptionStatus(
encryption_config.get("Status", "DISABLED")
)
stream.kms_key_arn = encryption_config.get("KeyARN", "")
stream.delivery_stream_type = describe_stream.get(
"DeliveryStreamDescription", {}
).get("DeliveryStreamType", "")
source_config = describe_stream.get("DeliveryStreamDescription", {}).get(
"Source", {}
)
stream.source = Source(
direct_put=DirectPutSourceDescription(
troughput_hint_in_mb_per_sec=source_config.get(
"DirectPutSourceDescription", {}
).get("TroughputHintInMBPerSec", 0)
),
kinesis_stream=KinesisStreamSourceDescription(
kinesis_stream_arn=source_config.get(
"KinesisStreamSourceDescription", {}
).get("KinesisStreamARN", "")
),
msk=MSKSourceDescription(
msk_cluster_arn=source_config.get("MSKSourceDescription", {}).get(
"MSKClusterARN", ""
)
),
database=DatabaseSourceDescription(
endpoint=source_config.get("DatabaseSourceDescription", {}).get(
"Endpoint", ""
)
),
)
except ClientError as error:
logger.error(
f"{stream.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
@@ -85,6 +146,39 @@ class EncryptionStatus(Enum):
DISABLING_FAILED = "DISABLING_FAILED"
class DirectPutSourceDescription(BaseModel):
"""Model for the DirectPut source of a Firehose stream"""
troughput_hint_in_mb_per_sec: int = Field(default_factory=int)
class KinesisStreamSourceDescription(BaseModel):
"""Model for the KinesisStream source of a Firehose stream"""
kinesis_stream_arn: str = Field(default_factory=str)
class MSKSourceDescription(BaseModel):
"""Model for the MSK source of a Firehose stream"""
msk_cluster_arn: str = Field(default_factory=str)
class DatabaseSourceDescription(BaseModel):
"""Model for the Database source of a Firehose stream"""
endpoint: str = Field(default_factory=str)
class Source(BaseModel):
"""Model for the source of a Firehose stream"""
direct_put: Optional[DirectPutSourceDescription]
kinesis_stream: Optional[KinesisStreamSourceDescription]
msk: Optional[MSKSourceDescription]
database: Optional[DatabaseSourceDescription]
class DeliveryStream(BaseModel):
"""Model for a Firehose Delivery Stream"""
@@ -94,3 +188,5 @@ class DeliveryStream(BaseModel):
kms_key_arn: Optional[str] = Field(default_factory=str)
kms_encryption: Optional[str] = Field(default_factory=str)
tags: Optional[List[Dict[str, str]]] = Field(default_factory=list)
delivery_stream_type: Optional[str] = Field(default_factory=str)
source: Source = Field(default_factory=Source)

View File

@@ -3,6 +3,8 @@ from typing import List
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.firehose.firehose_client import firehose_client
from prowler.providers.aws.services.firehose.firehose_service import EncryptionStatus
from prowler.providers.aws.services.kinesis.kinesis_client import kinesis_client
from prowler.providers.aws.services.kinesis.kinesis_service import EncryptionType
class firehose_stream_encrypted_at_rest(Check):
@@ -22,14 +24,22 @@ class firehose_stream_encrypted_at_rest(Check):
findings = []
for stream in firehose_client.delivery_streams.values():
report = Check_Report_AWS(metadata=self.metadata(), resource=stream)
report.status = "PASS"
report.status_extended = (
f"Firehose Stream {stream.name} does have at rest encryption enabled."
)
report.status = "FAIL"
report.status_extended = f"Firehose Stream {stream.name} does not have at rest encryption enabled or the source stream is not encrypted."
if stream.kms_encryption != EncryptionStatus.ENABLED:
report.status = "FAIL"
report.status_extended = f"Firehose Stream {stream.name} does not have at rest encryption enabled."
# Encrypted Kinesis Stream source
if stream.delivery_stream_type == "KinesisStreamAsSource":
source_stream = kinesis_client.streams.get(
stream.source.kinesis_stream.kinesis_stream_arn
)
if source_stream.encrypted_at_rest != EncryptionType.NONE:
report.status = "PASS"
report.status_extended = f"Firehose Stream {stream.name} does not have at rest encryption enabled but the source stream {source_stream.name} has at rest encryption enabled."
# Check if the stream has encryption enabled directly
elif stream.kms_encryption == EncryptionStatus.ENABLED:
report.status = "PASS"
report.status_extended = f"Firehose Stream {stream.name} does have at rest encryption enabled."
findings.append(report)

View File

@@ -5,6 +5,14 @@ from prowler.providers.aws.services.iam.iam_client import iam_client
class iam_no_custom_policy_permissive_role_assumption(Check):
def execute(self) -> Check_Report_AWS:
findings = []
def resource_has_wildcard(resource):
if isinstance(resource, str):
return "*" in resource
if isinstance(resource, list):
return any("*" in r for r in resource)
return False
for policy in iam_client.policies:
# Check only custom policies
if policy.type == "Custom":
@@ -12,6 +20,7 @@ class iam_no_custom_policy_permissive_role_assumption(Check):
report.region = iam_client.region
report.status = "PASS"
report.status_extended = f"Custom Policy {policy.name} does not allow permissive STS Role assumption."
if policy.document:
if not isinstance(policy.document["Statement"], list):
policy_statements = [policy.document["Statement"]]
@@ -19,30 +28,23 @@ class iam_no_custom_policy_permissive_role_assumption(Check):
policy_statements = policy.document["Statement"]
for statement in policy_statements:
if (
statement["Effect"] == "Allow"
statement.get("Effect") == "Allow"
and "Action" in statement
and "Resource" in statement
and "*" in statement["Resource"]
and resource_has_wildcard(statement["Resource"])
):
if isinstance(statement["Action"], list):
for action in statement["Action"]:
if (
action == "sts:AssumeRole"
or action == "sts:*"
or action == "*"
):
report.status = "FAIL"
report.status_extended = f"Custom Policy {policy.name} allows permissive STS Role assumption."
break
else:
if (
statement["Action"] == "sts:AssumeRole"
or statement["Action"] == "sts:*"
or statement["Action"] == "*"
):
actions = (
statement["Action"]
if isinstance(statement["Action"], list)
else [statement["Action"]]
)
for action in actions:
if action in ["sts:AssumeRole", "sts:*", "*"]:
report.status = "FAIL"
report.status_extended = f"Custom Policy {policy.name} allows permissive STS Role assumption."
break
break
if report.status == "FAIL":
break
findings.append(report)

View File

@@ -1,6 +1,5 @@
from dataclasses import dataclass
from enum import Enum
from typing import List, Optional
from typing import Optional
from azure.mgmt.storage import StorageManagementClient
from pydantic import BaseModel
@@ -33,7 +32,7 @@ class Storage(AzureService):
resouce_group_name = None
key_expiration_period_in_days = None
if storage_account.key_policy:
key_expiration_period_in_days = (
key_expiration_period_in_days = int(
storage_account.key_policy.key_expiration_period_in_days
)
replication_settings = ReplicationSettings(storage_account.sku.name)
@@ -181,30 +180,26 @@ class Storage(AzureService):
)
@dataclass
class DeleteRetentionPolicy:
class DeleteRetentionPolicy(BaseModel):
enabled: bool
days: int
@dataclass
class BlobProperties:
class BlobProperties(BaseModel):
id: str
name: str
type: str
default_service_version: str
container_delete_retention_policy: DeleteRetentionPolicy
versioning_enabled: bool = False
default_service_version: Optional[str] = None
versioning_enabled: Optional[bool] = None
@dataclass
class NetworkRuleSet:
class NetworkRuleSet(BaseModel):
bypass: str
default_action: str
@dataclass
class PrivateEndpointConnection:
class PrivateEndpointConnection(BaseModel):
id: str
name: str
type: str
@@ -228,20 +223,19 @@ class FileServiceProperties(BaseModel):
share_delete_retention_policy: DeleteRetentionPolicy
@dataclass
class Account:
class Account(BaseModel):
id: str
name: str
location: str
resouce_group_name: str
enable_https_traffic_only: bool
infrastructure_encryption: bool
infrastructure_encryption: Optional[bool] = None
allow_blob_public_access: bool
network_rule_set: NetworkRuleSet
encryption_type: str
minimum_tls_version: str
private_endpoint_connections: List[PrivateEndpointConnection]
key_expiration_period_in_days: str
location: str
private_endpoint_connections: list[PrivateEndpointConnection]
key_expiration_period_in_days: Optional[int] = None
replication_settings: ReplicationSettings = ReplicationSettings.STANDARD_LRS
allow_cross_tenant_replication: bool = True
allow_shared_key_access: bool = True

View File

@@ -10,6 +10,7 @@ def init_parser(self):
nargs="?",
help="Personal Access Token to log in against GitHub",
default=None,
metavar="GITHUB_PERSONAL_ACCESS_TOKEN",
)
github_auth_subparser.add_argument(
@@ -17,6 +18,7 @@ def init_parser(self):
nargs="?",
help="OAuth App Token to log in against GitHub",
default=None,
metavar="GITHUB_OAUTH_APP_TOKEN",
)
# GitHub App Authentication
@@ -25,10 +27,12 @@ def init_parser(self):
nargs="?",
help="GitHub App ID to log in against GitHub",
default=None,
metavar="GITHUB_APP_ID",
)
github_auth_subparser.add_argument(
"--github-app-key",
nargs="?",
help="GitHub App Key Path to log in against GitHub",
default=None,
metavar="GITHUB_APP_KEY",
)

View File

@@ -68,7 +68,7 @@ maintainers = [{name = "Prowler Engineering", email = "engineering@prowler.com"}
name = "prowler"
readme = "README.md"
requires-python = ">3.9.1,<3.13"
version = "5.8.0"
version = "5.8.2"
[project.scripts]
prowler = "prowler.__main__:prowler"

View File

@@ -27,6 +27,24 @@ def mock_make_api_call(self, operation_name, kwarg):
return make_api_call(self, operation_name, kwarg)
def mock_make_api_call_empty(self, operation_name, kwarg):
if operation_name == "DescribeLaunchTemplateVersions":
return {
"LaunchTemplateVersions": [
{
"VersionNumber": 1,
"LaunchTemplateData": {
"MetadataOptions": {
"HttpEndpoint": "",
"HttpTokens": "required",
}
},
}
]
}
return make_api_call(self, operation_name, kwarg)
def mock_make_api_call_not_required(self, operation_name, kwarg):
if operation_name == "DescribeLaunchTemplateVersions":
return {
@@ -134,6 +152,66 @@ class Test_ec2_launch_template_imdsv2_required:
)
assert result[0].resource_tags == []
@mock_aws
def test_launch_template_imdsv2_required_empty(self):
with mock.patch(
"botocore.client.BaseClient._make_api_call",
new=mock_make_api_call_empty,
):
ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1)
launch_template_name = "test-imdsv2-required-empty"
ec2_client.create_launch_template(
LaunchTemplateName=launch_template_name,
VersionDescription="Launch Template with IMDSv2 required",
LaunchTemplateData={
"InstanceType": "t1.micro",
"MetadataOptions": {
"HttpEndpoint": "",
"HttpTokens": "required",
},
},
)
launch_template_id = ec2_client.describe_launch_templates(
LaunchTemplateNames=[launch_template_name]
)["LaunchTemplates"][0]["LaunchTemplateId"]
from prowler.providers.aws.services.ec2.ec2_service import EC2
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,
),
mock.patch(
"prowler.providers.aws.services.ec2.ec2_launch_template_imdsv2_required.ec2_launch_template_imdsv2_required.ec2_client",
new=EC2(aws_provider),
),
):
# Test Check
from prowler.providers.aws.services.ec2.ec2_launch_template_imdsv2_required.ec2_launch_template_imdsv2_required import (
ec2_launch_template_imdsv2_required,
)
check = ec2_launch_template_imdsv2_required()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"EC2 Launch Template {launch_template_name} has metadata service disabled in the following versions: 1."
)
assert result[0].resource_id == launch_template_id
assert result[0].region == AWS_REGION_US_EAST_1
assert (
result[0].resource_arn
== f"arn:aws:ec2:{AWS_REGION_US_EAST_1}:123456789012:launch-template/{launch_template_id}"
)
assert result[0].resource_tags == []
@mock_aws
def test_launch_template_imdsv2_not_required(self):
with mock.patch(

View File

@@ -2,8 +2,13 @@ from boto3 import client
from moto import mock_aws
from prowler.providers.aws.services.firehose.firehose_service import (
DatabaseSourceDescription,
DirectPutSourceDescription,
EncryptionStatus,
Firehose,
KinesisStreamSourceDescription,
MSKSourceDescription,
Source,
)
from tests.providers.aws.utils import (
AWS_ACCOUNT_NUMBER,
@@ -152,3 +157,102 @@ class Test_Firehose_Service:
firehose.delivery_streams[arn].kms_key_arn
== f"arn:aws:kms:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:key/test-kms-key-id"
)
@mock_aws
def test_describe_delivery_stream_source_direct_put(self):
# Generate S3 client
s3_client = client("s3", region_name=AWS_REGION_EU_WEST_1)
s3_client.create_bucket(
Bucket="test-bucket",
CreateBucketConfiguration={"LocationConstraint": AWS_REGION_EU_WEST_1},
)
# Generate Firehose client
firehose_client = client("firehose", region_name=AWS_REGION_EU_WEST_1)
delivery_stream = firehose_client.create_delivery_stream(
DeliveryStreamName="test-delivery-stream",
DeliveryStreamType="DirectPut",
S3DestinationConfiguration={
"RoleARN": "arn:aws:iam::012345678901:role/firehose-role",
"BucketARN": "arn:aws:s3:::test-bucket",
"Prefix": "",
"BufferingHints": {"IntervalInSeconds": 300, "SizeInMBs": 5},
"CompressionFormat": "UNCOMPRESSED",
},
Tags=[{"Key": "key", "Value": "value"}],
)
arn = delivery_stream["DeliveryStreamARN"]
# Firehose Client for this test class
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
firehose = Firehose(aws_provider)
assert len(firehose.delivery_streams) == 1
assert firehose.delivery_streams[arn].delivery_stream_type == "DirectPut"
# Test Source structure
assert isinstance(firehose.delivery_streams[arn].source, Source)
assert isinstance(
firehose.delivery_streams[arn].source.direct_put, DirectPutSourceDescription
)
assert isinstance(
firehose.delivery_streams[arn].source.kinesis_stream,
KinesisStreamSourceDescription,
)
assert isinstance(
firehose.delivery_streams[arn].source.msk, MSKSourceDescription
)
assert isinstance(
firehose.delivery_streams[arn].source.database, DatabaseSourceDescription
)
@mock_aws
def test_describe_delivery_stream_source_kinesis_stream(self):
# Generate Kinesis client
kinesis_client = client("kinesis", region_name=AWS_REGION_EU_WEST_1)
kinesis_client.create_stream(
StreamName="test-kinesis-stream",
ShardCount=1,
)
kinesis_stream_arn = f"arn:aws:kinesis:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:stream/test-kinesis-stream"
# Generate Firehose client
firehose_client = client("firehose", region_name=AWS_REGION_EU_WEST_1)
delivery_stream = firehose_client.create_delivery_stream(
DeliveryStreamName="test-delivery-stream",
DeliveryStreamType="KinesisStreamAsSource",
KinesisStreamSourceConfiguration={
"KinesisStreamARN": kinesis_stream_arn,
"RoleARN": "arn:aws:iam::012345678901:role/firehose-role",
},
S3DestinationConfiguration={
"RoleARN": "arn:aws:iam::012345678901:role/firehose-role",
"BucketARN": "arn:aws:s3:::test-bucket",
"Prefix": "",
"BufferingHints": {"IntervalInSeconds": 300, "SizeInMBs": 5},
"CompressionFormat": "UNCOMPRESSED",
},
Tags=[{"Key": "key", "Value": "value"}],
)
arn = delivery_stream["DeliveryStreamARN"]
# Firehose Client for this test class
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
firehose = Firehose(aws_provider)
assert len(firehose.delivery_streams) == 1
assert (
firehose.delivery_streams[arn].delivery_stream_type
== "KinesisStreamAsSource"
)
# Test Source structure
assert isinstance(firehose.delivery_streams[arn].source, Source)
assert isinstance(
firehose.delivery_streams[arn].source.kinesis_stream,
KinesisStreamSourceDescription,
)
assert (
firehose.delivery_streams[arn].source.kinesis_stream.kinesis_stream_arn
== kinesis_stream_arn
)

View File

@@ -198,7 +198,7 @@ class Test_firehose_stream_encrypted_at_rest:
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Firehose Stream {stream_name} does not have at rest encryption enabled."
== f"Firehose Stream {stream_name} does not have at rest encryption enabled or the source stream is not encrypted."
)
@mock_aws
@@ -253,5 +253,74 @@ class Test_firehose_stream_encrypted_at_rest:
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Firehose Stream {stream_name} does not have at rest encryption enabled."
== f"Firehose Stream {stream_name} does not have at rest encryption enabled or the source stream is not encrypted."
)
@mock_aws
def test_stream_kinesis_source_encrypted(self):
# Generate Kinesis client
kinesis_client = client("kinesis", region_name=AWS_REGION_EU_WEST_1)
kinesis_client.create_stream(
StreamName="test-kinesis-stream",
ShardCount=1,
)
kinesis_stream_arn = f"arn:aws:kinesis:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:stream/test-kinesis-stream"
# Enable encryption on the Kinesis stream
kinesis_client.start_stream_encryption(
StreamName="test-kinesis-stream",
EncryptionType="KMS",
KeyId=f"arn:aws:kms:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:key/test-kms-key-id",
)
# Generate Firehose client
firehose_client = client("firehose", region_name=AWS_REGION_EU_WEST_1)
delivery_stream = firehose_client.create_delivery_stream(
DeliveryStreamName="test-delivery-stream",
DeliveryStreamType="KinesisStreamAsSource",
KinesisStreamSourceConfiguration={
"KinesisStreamARN": kinesis_stream_arn,
"RoleARN": "arn:aws:iam::012345678901:role/firehose-role",
},
S3DestinationConfiguration={
"RoleARN": "arn:aws:iam::012345678901:role/firehose-role",
"BucketARN": "arn:aws:s3:::test-bucket",
"Prefix": "",
"BufferingHints": {"IntervalInSeconds": 300, "SizeInMBs": 5},
"CompressionFormat": "UNCOMPRESSED",
},
Tags=[{"Key": "key", "Value": "value"}],
)
arn = delivery_stream["DeliveryStreamARN"]
stream_name = arn.split("/")[-1]
from prowler.providers.aws.services.firehose.firehose_service import Firehose
from prowler.providers.aws.services.kinesis.kinesis_service import Kinesis
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with mock.patch(
"prowler.providers.aws.services.firehose.firehose_stream_encrypted_at_rest.firehose_stream_encrypted_at_rest.firehose_client",
new=Firehose(aws_provider),
):
with mock.patch(
"prowler.providers.aws.services.firehose.firehose_stream_encrypted_at_rest.firehose_stream_encrypted_at_rest.kinesis_client",
new=Kinesis(aws_provider),
):
# Test Check
from prowler.providers.aws.services.firehose.firehose_stream_encrypted_at_rest.firehose_stream_encrypted_at_rest import (
firehose_stream_encrypted_at_rest,
)
check = firehose_stream_encrypted_at_rest()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Firehose Stream {stream_name} does not have at rest encryption enabled but the source stream test-kinesis-stream has at rest encryption enabled."
)

View File

@@ -232,3 +232,179 @@ class Test_iam_no_custom_policy_permissive_role_assumption:
result[1].status_extended,
)
assert result[1].resource_id == policy_name_permissive
@mock_aws
def test_policy_resource_with_embedded_wildcard_in_arn(self):
iam_client = client("iam")
policy_name = "policy_with_wildcard_in_arn"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::*:role/eks-terraform-*",
}
],
}
arn = iam_client.create_policy(
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
)["Policy"]["Arn"]
from prowler.providers.aws.services.iam.iam_service import IAM
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.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption.iam_client",
new=IAM(aws_provider),
):
from prowler.providers.aws.services.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption import (
iam_no_custom_policy_permissive_role_assumption,
)
check = iam_no_custom_policy_permissive_role_assumption()
result = check.execute()
assert result[0].status == "FAIL"
assert result[0].resource_arn == arn
assert search(
"allows permissive STS Role assumption", result[0].status_extended
)
assert result[0].resource_id == policy_name
@mock_aws
def test_policy_resource_list_containing_wildcard(self):
iam_client = client("iam")
policy_name = "policy_with_resource_list"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": ["arn:aws:iam::123456789012:role/SomeRole", "*"],
}
],
}
arn = iam_client.create_policy(
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
)["Policy"]["Arn"]
from prowler.providers.aws.services.iam.iam_service import IAM
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.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption.iam_client",
new=IAM(aws_provider),
):
from prowler.providers.aws.services.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption import (
iam_no_custom_policy_permissive_role_assumption,
)
check = iam_no_custom_policy_permissive_role_assumption()
result = check.execute()
assert result[0].status == "FAIL"
assert result[0].resource_arn == arn
assert search(
"allows permissive STS Role assumption", result[0].status_extended
)
assert result[0].resource_id == policy_name
@mock_aws
def test_policy_resource_list_with_arn_wildcards_should_fail(self):
iam_client = client("iam")
policy_name = "policy_list_wildcard_arn"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": [
"arn:aws:iam::123456789012:role/eks-admin",
"arn:aws:iam::*:role/eks-*",
],
}
],
}
arn = iam_client.create_policy(
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
)["Policy"]["Arn"]
from prowler.providers.aws.services.iam.iam_service import IAM
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.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption.iam_client",
new=IAM(aws_provider),
):
from prowler.providers.aws.services.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption import (
iam_no_custom_policy_permissive_role_assumption,
)
check = iam_no_custom_policy_permissive_role_assumption()
result = check.execute()
assert result[0].status == "FAIL"
assert result[0].resource_arn == arn
assert search(
"allows permissive STS Role assumption", result[0].status_extended
)
@mock_aws
def test_policy_resource_list_with_only_wildcarded_arns_should_fail(self):
iam_client = client("iam")
policy_name = "policy_list_scoped_wildcards_only"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": [
"arn:aws:iam::*:role/team-*",
"arn:aws:iam::*:role/dev-*",
],
}
],
}
arn = iam_client.create_policy(
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
)["Policy"]["Arn"]
from prowler.providers.aws.services.iam.iam_service import IAM
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.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption.iam_client",
new=IAM(aws_provider),
):
from prowler.providers.aws.services.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption import (
iam_no_custom_policy_permissive_role_assumption,
)
check = iam_no_custom_policy_permissive_role_assumption()
result = check.execute()
assert result[0].status == "FAIL"
assert result[0].resource_arn == arn
assert search(
"allows permissive STS Role assumption", result[0].status_extended
)

View File

@@ -91,6 +91,21 @@ class Test_monitor_diagnostic_settings_exists:
)
from prowler.providers.azure.services.storage.storage_service import (
Account,
BlobProperties,
DeleteRetentionPolicy,
NetworkRuleSet,
)
# Create a valid BlobProperties instance
valid_blob_properties = BlobProperties(
id="id",
name="name",
type="type",
default_service_version="default_service_version",
container_delete_retention_policy=DeleteRetentionPolicy(
enabled=False, days=0
),
versioning_enabled=True,
)
monitor_client.diagnostics_settings = {
@@ -138,42 +153,34 @@ class Test_monitor_diagnostic_settings_exists:
name="storageaccountname1",
resouce_group_name="rg",
enable_https_traffic_only=True,
infrastructure_encryption="Enabled",
infrastructure_encryption=True,
allow_blob_public_access=True,
network_rule_set="AllowAll",
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="Microsoft.CustomerManagedKeyVault",
minimum_tls_version="TLS1_2",
private_endpoint_connections=[],
key_expiration_period_in_days=365,
key_expiration_period_in_days="365",
location="euwest",
blob_properties=mock.MagicMock(
id="id",
name="name",
type="type",
default_service_version="default_service_version",
container_delete_retention_policy="container_delete_retention_policy",
),
blob_properties=valid_blob_properties,
),
Account(
id="/subscriptions/1224a5-123a-123a-123a-1234567890ab/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/storageaccountname2",
name="storageaccountname2",
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption="Enabled",
infrastructure_encryption=True,
allow_blob_public_access=False,
network_rule_set="AllowAll",
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="Microsoft.Storage",
minimum_tls_version="TLS1_2",
private_endpoint_connections=[],
key_expiration_period_in_days=365,
key_expiration_period_in_days="365",
location="euwest",
blob_properties=mock.MagicMock(
id="id",
name="name",
type="type",
default_service_version="default_service_version",
container_delete_retention_policy="container_delete_retention_policy",
),
blob_properties=valid_blob_properties,
),
]
}

View File

@@ -78,6 +78,9 @@ class Test_monitor_storage_account_with_activity_logs_cmk_encrypted:
)
from prowler.providers.azure.services.storage.storage_service import (
Account,
BlobProperties,
DeleteRetentionPolicy,
NetworkRuleSet,
)
monitor_client.diagnostics_settings = {
@@ -125,20 +128,25 @@ class Test_monitor_storage_account_with_activity_logs_cmk_encrypted:
name="storageaccountname1",
resouce_group_name="rg",
enable_https_traffic_only=True,
infrastructure_encryption="Enabled",
infrastructure_encryption=True, # bool
allow_blob_public_access=True,
network_rule_set="AllowAll",
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="Microsoft.CustomerManagedKeyVault",
minimum_tls_version="TLS1_2",
private_endpoint_connections=[],
key_expiration_period_in_days=365,
key_expiration_period_in_days="365", # str
location="euwest",
blob_properties=mock.MagicMock(
blob_properties=BlobProperties(
id="id",
name="name",
type="type",
default_service_version="default_service_version",
container_delete_retention_policy="container_delete_retention_policy",
container_delete_retention_policy=DeleteRetentionPolicy(
enabled=True, days=7
),
versioning_enabled=True,
),
),
Account(
@@ -146,20 +154,25 @@ class Test_monitor_storage_account_with_activity_logs_cmk_encrypted:
name="storageaccountname2",
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption="Enabled",
infrastructure_encryption=True, # bool
allow_blob_public_access=False,
network_rule_set="AllowAll",
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="Microsoft.Storage",
minimum_tls_version="TLS1_2",
private_endpoint_connections=[],
key_expiration_period_in_days=365,
key_expiration_period_in_days="365", # str
location="euwest",
blob_properties=mock.MagicMock(
blob_properties=BlobProperties(
id="id",
name="name",
type="type",
default_service_version="default_service_version",
container_delete_retention_policy="container_delete_retention_policy",
container_delete_retention_policy=DeleteRetentionPolicy(
enabled=True, days=7
),
versioning_enabled=False,
),
),
]

View File

@@ -78,6 +78,9 @@ class Test_monitor_storage_account_with_activity_logs_is_private:
)
from prowler.providers.azure.services.storage.storage_service import (
Account,
BlobProperties,
DeleteRetentionPolicy,
NetworkRuleSet,
)
monitor_client.diagnostics_settings = {
@@ -125,20 +128,25 @@ class Test_monitor_storage_account_with_activity_logs_is_private:
name="storageaccountname1",
resouce_group_name="rg",
enable_https_traffic_only=True,
infrastructure_encryption="Enabled",
infrastructure_encryption=True,
allow_blob_public_access=True,
network_rule_set="AllowAll",
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="Microsoft.Storage",
minimum_tls_version="TLS1_2",
private_endpoint_connections=[],
key_expiration_period_in_days=365,
location="euwest",
blob_properties=mock.MagicMock(
blob_properties=BlobProperties(
id="id",
name="name",
type="type",
default_service_version="default_service_version",
container_delete_retention_policy="container_delete_retention_policy",
container_delete_retention_policy=DeleteRetentionPolicy(
enabled=True, days=7
),
versioning_enabled=True,
),
),
Account(
@@ -146,20 +154,25 @@ class Test_monitor_storage_account_with_activity_logs_is_private:
name="storageaccountname2",
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption="Enabled",
infrastructure_encryption=True,
allow_blob_public_access=False,
network_rule_set="AllowAll",
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="Microsoft.Storage",
minimum_tls_version="TLS1_2",
private_endpoint_connections=[],
key_expiration_period_in_days=365,
location="euwest",
blob_properties=mock.MagicMock(
blob_properties=BlobProperties(
id="id",
name="name",
type="type",
default_service_version="default_service_version",
container_delete_retention_policy="container_delete_retention_policy",
container_delete_retention_policy=DeleteRetentionPolicy(
enabled=True, days=7
),
versioning_enabled=False,
),
),
]

View File

@@ -1,7 +1,10 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.azure.services.storage.storage_service import Account
from prowler.providers.azure.services.storage.storage_service import (
Account,
NetworkRuleSet,
)
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
set_mocked_azure_provider,
@@ -40,16 +43,18 @@ class Test_storage_account_key_access_disabled:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
encryption_type=None,
minimum_tls_version=None,
allow_blob_public_access=True,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version="TLS1_2",
private_endpoint_connections=[],
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
allow_shared_key_access=True,
)
]
@@ -91,16 +96,18 @@ class Test_storage_account_key_access_disabled:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
encryption_type=None,
minimum_tls_version=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version="TLS1_2",
private_endpoint_connections=[],
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
allow_shared_key_access=False,
)
]

View File

@@ -1,7 +1,10 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.azure.services.storage.storage_service import Account
from prowler.providers.azure.services.storage.storage_service import (
Account,
NetworkRuleSet,
)
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
set_mocked_azure_provider,
@@ -40,16 +43,18 @@ class Test_storage_blob_public_access_level_is_disabled:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version="TLS1_2",
private_endpoint_connections=[],
allow_blob_public_access=True,
network_rule_set=None,
encryption_type=None,
minimum_tls_version=None,
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
)
]
}
@@ -90,16 +95,18 @@ class Test_storage_blob_public_access_level_is_disabled:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version="TLS1_2",
private_endpoint_connections=[],
allow_blob_public_access=False,
network_rule_set=None,
encryption_type=None,
minimum_tls_version=None,
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
)
]
}

View File

@@ -45,23 +45,28 @@ class Test_storage_blob_versioning_is_enabled:
new=storage_client,
),
):
from prowler.providers.azure.services.storage.storage_service import Account
from prowler.providers.azure.services.storage.storage_service import (
Account,
NetworkRuleSet,
)
storage_client.storage_accounts = {
AZURE_SUBSCRIPTION_ID: [
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version=None,
minimum_tls_version="TLS1_2",
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
private_endpoint_connections=[],
blob_properties=storage_account_blob_properties,
)
]
@@ -92,12 +97,13 @@ class Test_storage_blob_versioning_is_enabled:
Account,
BlobProperties,
DeleteRetentionPolicy,
NetworkRuleSet,
)
storage_account_blob_properties = BlobProperties(
id=None,
name=None,
type=None,
id="id",
name="name",
type="type",
default_service_version=None,
container_delete_retention_policy=DeleteRetentionPolicy(
enabled=False, days=0
@@ -109,16 +115,18 @@ class Test_storage_blob_versioning_is_enabled:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version=None,
minimum_tls_version="TLS1_2",
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
private_endpoint_connections=[],
blob_properties=storage_account_blob_properties,
)
]
@@ -158,12 +166,13 @@ class Test_storage_blob_versioning_is_enabled:
Account,
BlobProperties,
DeleteRetentionPolicy,
NetworkRuleSet,
)
storage_account_blob_properties = BlobProperties(
id=None,
name=None,
type=None,
id="id",
name="name",
type="type",
default_service_version=None,
container_delete_retention_policy=DeleteRetentionPolicy(
enabled=False, days=0
@@ -175,16 +184,18 @@ class Test_storage_blob_versioning_is_enabled:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version=None,
minimum_tls_version="TLS1_2",
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
private_endpoint_connections=[],
blob_properties=storage_account_blob_properties,
)
]

View File

@@ -1,7 +1,10 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.azure.services.storage.storage_service import Account
from prowler.providers.azure.services.storage.storage_service import (
Account,
NetworkRuleSet,
)
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
set_mocked_azure_provider,
@@ -40,16 +43,18 @@ class Test_storage_cross_tenant_replication_disabled:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
encryption_type=None,
minimum_tls_version=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version="TLS1_2",
private_endpoint_connections=[],
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
allow_cross_tenant_replication=True,
)
]
@@ -91,16 +96,18 @@ class Test_storage_cross_tenant_replication_disabled:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
encryption_type=None,
minimum_tls_version=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version="TLS1_2",
private_endpoint_connections=[],
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
allow_cross_tenant_replication=False,
)
]

View File

@@ -43,18 +43,18 @@ class Test_storage_default_network_access_rule_is_denied:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
default_action="Allow", bypass="AzureServices"
bypass="AzureServices", default_action="Allow"
),
encryption_type=None,
minimum_tls_version=None,
encryption_type="None",
minimum_tls_version="TLS1_2",
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
private_endpoint_connections=[],
)
]
}
@@ -95,18 +95,18 @@ class Test_storage_default_network_access_rule_is_denied:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
default_action="Deny", bypass="AzureServices"
),
encryption_type=None,
minimum_tls_version=None,
encryption_type="None",
minimum_tls_version="TLS1_2",
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
private_endpoint_connections=[],
)
]
}

View File

@@ -1,7 +1,10 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.azure.services.storage.storage_service import Account
from prowler.providers.azure.services.storage.storage_service import (
Account,
NetworkRuleSet,
)
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
set_mocked_azure_provider,
@@ -40,16 +43,18 @@ class Test_storage_default_to_entra_authorization_enabled:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=False,
network_rule_set=None,
encryption_type=None,
minimum_tls_version=None,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version="TLS1_2",
private_endpoint_connections=[],
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
default_to_entra_authorization=True,
)
]
@@ -91,16 +96,18 @@ class Test_storage_default_to_entra_authorization_enabled:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=False,
network_rule_set=None,
encryption_type=None,
minimum_tls_version=None,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version="TLS1_2",
private_endpoint_connections=[],
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
default_to_entra_authorization=False,
)
]

View File

@@ -43,18 +43,18 @@ class Test_storage_ensure_azure_services_are_trusted_to_access_is_enabled:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="None", default_action="Deny"
),
encryption_type=None,
minimum_tls_version=None,
encryption_type="None",
minimum_tls_version="TLS1_2",
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
private_endpoint_connections=[],
)
]
}
@@ -95,18 +95,18 @@ class Test_storage_ensure_azure_services_are_trusted_to_access_is_enabled:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type=None,
minimum_tls_version=None,
encryption_type="None",
minimum_tls_version="TLS1_2",
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
private_endpoint_connections=[],
)
]
}

View File

@@ -1,7 +1,10 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.azure.services.storage.storage_service import Account
from prowler.providers.azure.services.storage.storage_service import (
Account,
NetworkRuleSet,
)
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
set_mocked_azure_provider,
@@ -40,16 +43,18 @@ class Test_storage_ensure_encryption_with_customer_managed_keys:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
minimum_tls_version="TLS1_2",
private_endpoint_connections=[],
encryption_type="None",
minimum_tls_version=None,
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
)
]
}
@@ -90,16 +95,18 @@ class Test_storage_ensure_encryption_with_customer_managed_keys:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
minimum_tls_version="TLS1_2",
private_endpoint_connections=[],
encryption_type="Microsoft.Keyvault",
minimum_tls_version=None,
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
)
]
}

View File

@@ -5,6 +5,7 @@ from prowler.providers.azure.services.storage.storage_service import (
Account,
DeleteRetentionPolicy,
FileServiceProperties,
NetworkRuleSet,
)
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
@@ -44,16 +45,18 @@ class Test_storage_ensure_file_shares_soft_delete_is_enabled:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version=None,
minimum_tls_version="TLS1_2",
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
private_endpoint_connections=[],
file_service_properties=None,
)
]
@@ -93,16 +96,18 @@ class Test_storage_ensure_file_shares_soft_delete_is_enabled:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version=None,
minimum_tls_version="TLS1_2",
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
private_endpoint_connections=[],
file_service_properties=file_service_properties,
)
]
@@ -151,16 +156,18 @@ class Test_storage_ensure_file_shares_soft_delete_is_enabled:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version=None,
minimum_tls_version="TLS1_2",
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
private_endpoint_connections=[],
file_service_properties=file_service_properties,
)
]

View File

@@ -1,7 +1,10 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.azure.services.storage.storage_service import Account
from prowler.providers.azure.services.storage.storage_service import (
Account,
NetworkRuleSet,
)
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
set_mocked_azure_provider,
@@ -40,16 +43,18 @@ class Test_storage_ensure_minimum_tls_version_12:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version="TLS1_1",
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
private_endpoint_connections=[],
)
]
}
@@ -90,16 +95,18 @@ class Test_storage_ensure_minimum_tls_version_12:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version="TLS1_2",
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
private_endpoint_connections=[],
)
]
}

View File

@@ -3,6 +3,7 @@ from uuid import uuid4
from prowler.providers.azure.services.storage.storage_service import (
Account,
NetworkRuleSet,
PrivateEndpointConnection,
)
from tests.providers.azure.azure_fixtures import (
@@ -45,16 +46,18 @@ class Test_storage_ensure_private_endpoints_in_storage_accounts:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version=None,
minimum_tls_version="TLS1_2",
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
private_endpoint_connections=[],
)
]
}
@@ -97,22 +100,24 @@ class Test_storage_ensure_private_endpoints_in_storage_accounts:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version=None,
minimum_tls_version="TLS1_2",
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=PrivateEndpointConnection(
id=str(
uuid4(),
),
name="Test Private Endpoint Connection",
type="Test Type",
),
private_endpoint_connections=[
PrivateEndpointConnection(
id="f1ef2e48-978a-4b0e-b34f-e6c34a9e0724",
name="Test Private Endpoint Connection",
type="Test Type",
)
],
)
]
}

View File

@@ -5,6 +5,7 @@ from prowler.providers.azure.services.storage.storage_service import (
Account,
BlobProperties,
DeleteRetentionPolicy,
NetworkRuleSet,
)
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
@@ -45,16 +46,18 @@ class Test_storage_ensure_soft_delete_is_enabled:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version=None,
minimum_tls_version="TLS1_2",
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
private_endpoint_connections=[],
blob_properties=storage_account_blob_properties,
)
]
@@ -85,29 +88,32 @@ class Test_storage_ensure_soft_delete_is_enabled:
storage_account_name = "Test Storage Account"
storage_client = mock.MagicMock
storage_account_blob_properties = BlobProperties(
id=None,
name=None,
type=None,
id="id",
name="name",
type="type",
default_service_version=None,
container_delete_retention_policy=DeleteRetentionPolicy(
enabled=False, days=7
),
versioning_enabled=False,
)
storage_client.storage_accounts = {
AZURE_SUBSCRIPTION_ID: [
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version=None,
minimum_tls_version="TLS1_2",
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
private_endpoint_connections=[],
blob_properties=storage_account_blob_properties,
)
]
@@ -147,29 +153,32 @@ class Test_storage_ensure_soft_delete_is_enabled:
storage_account_name = "Test Storage Account"
storage_client = mock.MagicMock
storage_account_blob_properties = BlobProperties(
id=None,
name=None,
type=None,
id="id",
name="name",
type="type",
default_service_version=None,
container_delete_retention_policy=DeleteRetentionPolicy(
enabled=True, days=7
),
versioning_enabled=True,
)
storage_client.storage_accounts = {
AZURE_SUBSCRIPTION_ID: [
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version=None,
minimum_tls_version="TLS1_2",
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
private_endpoint_connections=[],
blob_properties=storage_account_blob_properties,
)
]

View File

@@ -3,6 +3,7 @@ from uuid import uuid4
from prowler.providers.azure.services.storage.storage_service import (
Account,
NetworkRuleSet,
ReplicationSettings,
)
from tests.providers.azure.azure_fixtures import (
@@ -43,16 +44,18 @@ class Test_storage_geo_redundant_enabled:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=False,
network_rule_set=None,
encryption_type=None,
minimum_tls_version=None,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version="TLS1_2",
private_endpoint_connections=[],
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
replication_settings=ReplicationSettings.STANDARD_GRS,
)
]
@@ -94,16 +97,18 @@ class Test_storage_geo_redundant_enabled:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=False,
network_rule_set=None,
encryption_type=None,
minimum_tls_version=None,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version="TLS1_2",
private_endpoint_connections=[],
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
replication_settings=ReplicationSettings.STANDARD_LRS,
)
]

View File

@@ -1,7 +1,10 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.azure.services.storage.storage_service import Account
from prowler.providers.azure.services.storage.storage_service import (
Account,
NetworkRuleSet,
)
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
set_mocked_azure_provider,
@@ -40,16 +43,18 @@ class Test_storage_infrastructure_encryption_is_enabled:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version="TLS1_1",
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
private_endpoint_connections=[],
)
]
}
@@ -90,16 +95,18 @@ class Test_storage_infrastructure_encryption_is_enabled:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=True,
allow_blob_public_access=None,
network_rule_set=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version="TLS1_1",
minimum_tls_version="TLS1_2",
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
private_endpoint_connections=[],
)
]
}

View File

@@ -1,7 +1,10 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.azure.services.storage.storage_service import Account
from prowler.providers.azure.services.storage.storage_service import (
Account,
NetworkRuleSet,
)
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
set_mocked_azure_provider,
@@ -41,16 +44,18 @@ class Test_storage_key_rotation_90_dayss:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
key_expiration_period_in_days="91",
encryption_type="None",
minimum_tls_version="TLS1_1",
key_expiration_period_in_days=expiration_days,
minimum_tls_version="TLS1_2",
private_endpoint_connections=[],
location="westeurope",
private_endpoint_connections=None,
)
]
}
@@ -92,16 +97,18 @@ class Test_storage_key_rotation_90_dayss:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
key_expiration_period_in_days=90,
encryption_type="None",
minimum_tls_version="TLS1_2",
key_expiration_period_in_days=expiration_days,
private_endpoint_connections=[],
location="westeurope",
private_endpoint_connections=None,
)
]
}
@@ -142,16 +149,18 @@ class Test_storage_key_rotation_90_dayss:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
key_expiration_period_in_days=None,
encryption_type="None",
minimum_tls_version="TLS1_2",
key_expiration_period_in_days=None,
private_endpoint_connections=[],
location="westeurope",
private_endpoint_connections=None,
)
]
}

View File

@@ -1,7 +1,10 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.azure.services.storage.storage_service import Account
from prowler.providers.azure.services.storage.storage_service import (
Account,
NetworkRuleSet,
)
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
set_mocked_azure_provider,
@@ -40,16 +43,18 @@ class Test_storage_secure_transfer_required_is_enabled:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version="TLS1_1",
minimum_tls_version="TLS1_2",
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
private_endpoint_connections=[],
)
]
}
@@ -90,16 +95,18 @@ class Test_storage_secure_transfer_required_is_enabled:
Account(
id=storage_account_id,
name=storage_account_name,
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=True,
infrastructure_encryption=True,
allow_blob_public_access=None,
network_rule_set=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version="TLS1_1",
minimum_tls_version="TLS1_2",
key_expiration_period_in_days=None,
location="westeurope",
private_endpoint_connections=None,
private_endpoint_connections=[],
)
]
}

View File

@@ -5,6 +5,7 @@ from prowler.providers.azure.services.storage.storage_service import (
BlobProperties,
DeleteRetentionPolicy,
FileServiceProperties,
NetworkRuleSet,
ReplicationSettings,
Storage,
)
@@ -20,7 +21,7 @@ def mock_storage_get_storage_accounts(_):
name="name",
type="type",
default_service_version=None,
container_delete_retention_policy=None,
container_delete_retention_policy=DeleteRetentionPolicy(enabled=True, days=7),
)
retention_policy = DeleteRetentionPolicy(enabled=True, days=7)
file_service_properties = FileServiceProperties(
@@ -34,15 +35,17 @@ def mock_storage_get_storage_accounts(_):
Account(
id="id",
name="name",
resouce_group_name=None,
resouce_group_name="rg",
enable_https_traffic_only=False,
infrastructure_encryption=False,
allow_blob_public_access=None,
network_rule_set=None,
allow_blob_public_access=False,
network_rule_set=NetworkRuleSet(
bypass="AzureServices", default_action="Allow"
),
encryption_type="None",
minimum_tls_version=None,
minimum_tls_version="TLS1_2",
key_expiration_period_in_days=None,
private_endpoint_connections=None,
private_endpoint_connections=[],
location="westeurope",
blob_properties=blob_properties,
default_to_entra_authorization=True,
@@ -77,7 +80,7 @@ class Test_Storage_Service:
assert storage.storage_accounts[AZURE_SUBSCRIPTION_ID][0].name == "name"
assert (
storage.storage_accounts[AZURE_SUBSCRIPTION_ID][0].resouce_group_name
is None
== "rg"
)
assert (
storage.storage_accounts[AZURE_SUBSCRIPTION_ID][0].enable_https_traffic_only
@@ -89,10 +92,21 @@ class Test_Storage_Service:
)
assert (
storage.storage_accounts[AZURE_SUBSCRIPTION_ID][0].allow_blob_public_access
is None
is False
)
assert (
storage.storage_accounts[AZURE_SUBSCRIPTION_ID][0].network_rule_set is None
storage.storage_accounts[AZURE_SUBSCRIPTION_ID][0].network_rule_set
is not None
)
assert (
storage.storage_accounts[AZURE_SUBSCRIPTION_ID][0].network_rule_set.bypass
== "AzureServices"
)
assert (
storage.storage_accounts[AZURE_SUBSCRIPTION_ID][
0
].network_rule_set.default_action
== "Allow"
)
assert (
storage.storage_accounts[AZURE_SUBSCRIPTION_ID][0].encryption_type == "None"
@@ -102,7 +116,7 @@ class Test_Storage_Service:
)
assert (
storage.storage_accounts[AZURE_SUBSCRIPTION_ID][0].minimum_tls_version
is None
== "TLS1_2"
)
assert (
storage.storage_accounts[AZURE_SUBSCRIPTION_ID][
@@ -114,7 +128,7 @@ class Test_Storage_Service:
storage.storage_accounts[AZURE_SUBSCRIPTION_ID][
0
].private_endpoint_connections
is None
== []
)
assert storage.storage_accounts[AZURE_SUBSCRIPTION_ID][
0
@@ -123,7 +137,9 @@ class Test_Storage_Service:
name="name",
type="type",
default_service_version=None,
container_delete_retention_policy=None,
container_delete_retention_policy=DeleteRetentionPolicy(
enabled=True, days=7
),
)
assert storage.storage_accounts[AZURE_SUBSCRIPTION_ID][
0
@@ -173,7 +189,19 @@ class Test_Storage_Service:
storage.storage_accounts[AZURE_SUBSCRIPTION_ID][
0
].blob_properties.container_delete_retention_policy
is None
is not None
)
assert (
storage.storage_accounts[AZURE_SUBSCRIPTION_ID][
0
].blob_properties.container_delete_retention_policy.enabled
is True
)
assert (
storage.storage_accounts[AZURE_SUBSCRIPTION_ID][
0
].blob_properties.container_delete_retention_policy.days
== 7
)
def test_get_file_service_properties(self):

View File

@@ -7,6 +7,17 @@ All notable changes to the **Prowler UI** are documented in this file.
### 🚀 Added
### 🔄 Changed
### 🐞 Fixed
### Removed
---
## [v1.8.1] (Prowler 5.8.1)
### 🔄 Changed
- Latest new failed findings now use `GET /findings/latest` [(#8219)](https://github.com/prowler-cloud/prowler/pull/8219)
### Removed
- Validation of the provider's secret type during updates [(#8197)](https://github.com/prowler-cloud/prowler/pull/8197)
---

View File

@@ -13,7 +13,6 @@ import {
} from "@/lib";
import {
buildSecretConfig,
buildUpdateSecretConfig,
handleApiError,
handleApiResponse,
} from "@/lib/provider-credentials/build-crendentials";
@@ -194,8 +193,7 @@ export const updateCredentialsProvider = async (
) as ProviderType;
try {
const secret = buildUpdateSecretConfig(formData, providerType);
const { secretType, secret } = buildSecretConfig(formData, providerType);
const response = await fetch(url.toString(), {
method: "PATCH",
headers,
@@ -203,7 +201,7 @@ export const updateCredentialsProvider = async (
data: {
type: "provider-secrets",
id: credentialsId,
attributes: { secret },
attributes: { secret_type: secretType, secret },
},
}),
});

View File

@@ -1,8 +1,7 @@
import { Spacer } from "@nextui-org/react";
import { format, subDays } from "date-fns";
import { Suspense } from "react";
import { getFindings } from "@/actions/findings/findings";
import { getLatestFindings } from "@/actions/findings/findings";
import {
getFindingsBySeverity,
getFindingsByStatus,
@@ -129,15 +128,12 @@ const SSRDataNewFindingsTable = async () => {
const page = 1;
const sort = "severity,-inserted_at";
const twoDaysAgo = format(subDays(new Date(), 2), "yyyy-MM-dd");
const defaultFilters = {
"filter[status__in]": "FAIL",
"filter[delta__in]": "new",
"filter[inserted_at__gte]": twoDaysAgo,
"filter[status]": "FAIL",
"filter[delta]": "new",
};
const findingsData = await getFindings({
const findingsData = await getLatestFindings({
query: undefined,
page,
sort,
@@ -178,8 +174,7 @@ const SSRDataNewFindingsTable = async () => {
Latest new failing findings
</h3>
<p className="text-xs text-gray-500">
Showing the latest 10 new failing findings by severity from the last
2 days.
Showing the latest 10 new failing findings by severity.
</p>
</div>
<div className="absolute -top-6 right-0">

View File

@@ -1,7 +1,7 @@
import { Spacer } from "@nextui-org/react";
import { Suspense } from "react";
import { getProvider, getProviders } from "@/actions/providers";
import { getProviders } from "@/actions/providers";
import { getScans, getScansByState } from "@/actions/scans";
import {
AutoRefresh,
@@ -45,13 +45,9 @@ export default async function Scans({
connected: provider.attributes.connection.connected,
})) || [];
const providersCountConnected = await getProviders({
filters: { "filter[connected]": true },
pageSize: 50,
});
const thereIsNoProviders = !providersCountConnected?.data;
const thereIsNoProviders = !providersData?.data;
const thereIsNoProvidersConnected = providersCountConnected?.data?.every(
const thereIsNoProvidersConnected = providersData?.data?.every(
(provider: ProviderProps) => !provider.attributes.connection.connected,
);
@@ -123,34 +119,43 @@ const SSRDataTableScans = async ({
// Extract query from filters
const query = (filters["filter[search]"] as string) || "";
// Fetch scans data
const scansData = await getScans({ query, page, sort, filters, pageSize });
// Fetch scans data with provider information included
const scansData = await getScans({
query,
page,
sort,
filters,
pageSize,
include: "provider",
});
// Handle expanded scans data
const expandedScansData = await Promise.all(
scansData?.data?.map(async (scan: any) => {
// Process scans with provider information from included data
const expandedScansData =
scansData?.data?.map((scan: any) => {
const providerId = scan.relationships?.provider?.data?.id;
if (!providerId) {
return { ...scan, providerInfo: null };
}
const formData = new FormData();
formData.append("id", providerId);
// Find the provider data in the included array
const providerData = scansData.included?.find(
(item: any) => item.type === "providers" && item.id === providerId,
);
const providerData = await getProvider(formData);
if (providerData?.data) {
const { provider, uid, alias } = providerData.data.attributes;
return {
...scan,
providerInfo: { provider, uid, alias },
};
if (!providerData) {
return { ...scan, providerInfo: null };
}
return { ...scan, providerInfo: null };
}) || [],
);
return {
...scan,
providerInfo: {
provider: providerData.attributes.provider,
uid: providerData.attributes.uid,
alias: providerData.attributes.alias,
},
};
}) || [];
return (
<DataTable

View File

@@ -1,6 +1,5 @@
"use client";
import { InfoIcon } from "@/components/icons";
import { SelectViaAWS } from "@/components/providers/workflow/forms/select-credentials-type/aws";
import { SelectViaGCP } from "@/components/providers/workflow/forms/select-credentials-type/gcp";
import { ProviderType } from "@/types/providers";
@@ -24,28 +23,5 @@ export const CredentialsUpdateInfo = ({
return null;
};
return (
<div className="flex flex-col gap-4">
<p className="text-sm text-default-700">
To update provider credentials,{" "}
<strong>
the same type that was originally configured must be used.
</strong>
</p>
<div className="flex items-center rounded-lg border border-system-warning bg-system-warning-medium p-4 text-sm dark:text-default-300">
<InfoIcon className="mr-2 inline h-4 w-4 flex-shrink-0" />
<p>
If the provider was configured with static credentials, updates must
also use static credentials. If it was configured with a role in AWS
(or service account in GCP),{" "}
<strong>updates must use the same type.</strong>
</p>
</div>
<p className="text-sm font-bold text-default-700">
To switch from one type to another, the provider must be deleted and set
up again.
</p>
{renderSelectComponent()}
</div>
);
return <div className="flex flex-col gap-4">{renderSelectComponent()}</div>;
};

View File

@@ -121,44 +121,44 @@ export const useRelatedFilters = ({
if (shouldDeselectScan) {
updateFilter(FilterType.SCAN, null);
} else {
// Add provider if not already selected
if (scanProviderId && !currentProviders.includes(scanProviderId)) {
updateFilter(FilterType.PROVIDER_UID, [
...currentProviders,
scanProviderId,
]);
}
// } else {
// // Add provider if not already selected
// if (scanProviderId && !currentProviders.includes(scanProviderId)) {
// updateFilter(FilterType.PROVIDER_UID, [
// ...currentProviders,
// scanProviderId,
// ]);
// }
// Only add provider type if there are none selected
if (
scanProviderType &&
currentProviderTypes.length === 0 &&
!isManualDeselection.current
) {
updateFilter(FilterType.PROVIDER_TYPE, [scanProviderType]);
}
// // Only add provider type if there are none selected
// if (
// scanProviderType &&
// currentProviderTypes.length === 0 &&
// !isManualDeselection.current
// ) {
// updateFilter(FilterType.PROVIDER_TYPE, [scanProviderType]);
// }
}
}
// Handle provider selection logic
if (
currentProviders.length > 0 &&
deselectedProviders.length === 0 &&
!isManualDeselection.current
) {
const providerTypes = currentProviders
.map(getProviderType)
.filter((type): type is ProviderType => type !== null);
const selectedProviderTypes = Array.from(new Set(providerTypes));
// // Handle provider selection logic
// if (
// currentProviders.length > 0 &&
// deselectedProviders.length === 0 &&
// !isManualDeselection.current
// ) {
// const providerTypes = currentProviders
// .map(getProviderType)
// .filter((type): type is ProviderType => type !== null);
// const selectedProviderTypes = Array.from(new Set(providerTypes));
if (
selectedProviderTypes.length > 0 &&
currentProviderTypes.length === 0
) {
updateFilter(FilterType.PROVIDER_TYPE, selectedProviderTypes);
}
}
// if (
// selectedProviderTypes.length > 0 &&
// currentProviderTypes.length === 0
// ) {
// updateFilter(FilterType.PROVIDER_TYPE, selectedProviderTypes);
// }
// }
// Update available providers
if (currentProviderTypes.length > 0) {

View File

@@ -187,25 +187,6 @@ export const buildSecretConfig = (
return builder();
};
// Helper function to build secret for update (reuses existing logic)
export const buildUpdateSecretConfig = (
formData: FormData,
providerType: ProviderType,
) => {
// Reuse the same secret building logic as add, but only return the secret
const { secret } = buildSecretConfig(formData, providerType);
// Handle special case for M365 password field inconsistency
if (providerType === "m365") {
return {
...secret,
password: formData.get(ProviderCredentialFields.PASSWORD),
};
}
return secret;
};
// Helper function to handle API responses consistently
export const handleApiResponse = async (
response: Response,