mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-09 21:14:22 +00:00
fix(gcp): honour org-aggregated sinks in metric-filter checks (#11488)
Co-authored-by: Hugo P.Brito <hugopbrit@gmail.com>
This commit is contained in:
co-authored by
Hugo P.Brito
parent
466f1a3d73
commit
1c9afc714e
@@ -18,6 +18,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
### 🐞 Fixed
|
||||
|
||||
- GCP `logging_sink_created` now recognizes organization-level aggregated sinks with `includeChildren=True`, avoiding false failures for covered projects [(#11355)](https://github.com/prowler-cloud/prowler/pull/11355)
|
||||
- GCP `logging_log_metric_filter_and_alert_*` checks now recognize organization-level aggregated sinks with `includeChildren=True`, no longer false-failing projects covered by a central bucket-scoped metric + alert [(#11488)](https://github.com/prowler-cloud/prowler/pull/11488)
|
||||
- Jira integration no longer fails with `400 INVALID_INPUT` when a finding has empty fields [(#11474)](https://github.com/prowler-cloud/prowler/pull/11474)
|
||||
- GCP `iam_service_account_unused` now passes disabled service accounts instead of failing them, since a disabled account cannot authenticate or be used [(#11467)](https://github.com/prowler-cloud/prowler/pull/11467)
|
||||
- AWS AI Security Framework now renders in the dashboard instead of showing "No data found for this compliance", by adding the missing compliance view module [(#11470)](https://github.com/prowler-cloud/prowler/pull/11470)
|
||||
|
||||
+16
-6
@@ -1,5 +1,8 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||
from prowler.providers.gcp.services.logging.logging_client import logging_client
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
get_projects_covered_by_aggregated_metric,
|
||||
)
|
||||
from prowler.providers.gcp.services.monitoring.monitoring_client import (
|
||||
monitoring_client,
|
||||
)
|
||||
@@ -10,12 +13,10 @@ class logging_log_metric_filter_and_alert_for_audit_configuration_changes_enable
|
||||
):
|
||||
def execute(self) -> Check_Report_GCP:
|
||||
findings = []
|
||||
metric_filter = 'protoPayload.methodName="SetIamPolicy" AND protoPayload.serviceData.policyDelta.auditConfigDeltas:*'
|
||||
projects_with_metric = set()
|
||||
for metric in logging_client.metrics:
|
||||
if (
|
||||
'protoPayload.methodName="SetIamPolicy" AND protoPayload.serviceData.policyDelta.auditConfigDeltas:*'
|
||||
in metric.filter
|
||||
):
|
||||
if metric_filter in metric.filter:
|
||||
report = Check_Report_GCP(
|
||||
metadata=self.metadata(),
|
||||
resource=metric,
|
||||
@@ -33,6 +34,11 @@ class logging_log_metric_filter_and_alert_for_audit_configuration_changes_enable
|
||||
break
|
||||
findings.append(report)
|
||||
|
||||
# Credit projects whose logs are centrally monitored via an org-level
|
||||
# aggregated sink to a bucket-scoped metric + alert (instead of failing them).
|
||||
centrally_covered = get_projects_covered_by_aggregated_metric(
|
||||
logging_client, monitoring_client, metric_filter
|
||||
)
|
||||
for project in logging_client.project_ids:
|
||||
if project not in projects_with_metric:
|
||||
report = Check_Report_GCP(
|
||||
@@ -46,8 +52,12 @@ class logging_log_metric_filter_and_alert_for_audit_configuration_changes_enable
|
||||
else "GCP Project"
|
||||
),
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There are no log metric filters or alerts associated in project {project}."
|
||||
if project in centrally_covered:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Log metric filter {centrally_covered[project]} found with an alert, covering project {project} via an organization-level aggregated sink."
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There are no log metric filters or alerts associated in project {project}."
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
|
||||
+14
-6
@@ -1,5 +1,8 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||
from prowler.providers.gcp.services.logging.logging_client import logging_client
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
get_projects_covered_by_aggregated_metric,
|
||||
)
|
||||
from prowler.providers.gcp.services.monitoring.monitoring_client import (
|
||||
monitoring_client,
|
||||
)
|
||||
@@ -8,12 +11,10 @@ from prowler.providers.gcp.services.monitoring.monitoring_client import (
|
||||
class logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled(Check):
|
||||
def execute(self) -> Check_Report_GCP:
|
||||
findings = []
|
||||
metric_filter = 'resource.type="gcs_bucket" AND protoPayload.methodName="storage.setIamPermissions"'
|
||||
projects_with_metric = set()
|
||||
for metric in logging_client.metrics:
|
||||
if (
|
||||
'resource.type="gcs_bucket" AND protoPayload.methodName="storage.setIamPermissions"'
|
||||
in metric.filter
|
||||
):
|
||||
if metric_filter in metric.filter:
|
||||
metric_name = getattr(metric, "name", None) or "unknown"
|
||||
report = Check_Report_GCP(
|
||||
metadata=self.metadata(),
|
||||
@@ -36,6 +37,9 @@ class logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled(
|
||||
break
|
||||
findings.append(report)
|
||||
|
||||
centrally_covered = get_projects_covered_by_aggregated_metric(
|
||||
logging_client, monitoring_client, metric_filter
|
||||
)
|
||||
for project in logging_client.project_ids:
|
||||
if project not in projects_with_metric:
|
||||
project_obj = logging_client.projects.get(project)
|
||||
@@ -46,8 +50,12 @@ class logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled(
|
||||
location=logging_client.region,
|
||||
resource_name=(getattr(project_obj, "name", None) or "GCP Project"),
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There are no log metric filters or alerts associated in project {project}."
|
||||
if project in centrally_covered:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Log metric filter {centrally_covered[project]} found with an alert, covering project {project} via an organization-level aggregated sink."
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There are no log metric filters or alerts associated in project {project}."
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
|
||||
+14
-3
@@ -1,5 +1,8 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||
from prowler.providers.gcp.services.logging.logging_client import logging_client
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
get_projects_covered_by_aggregated_metric,
|
||||
)
|
||||
from prowler.providers.gcp.services.monitoring.monitoring_client import (
|
||||
monitoring_client,
|
||||
)
|
||||
@@ -10,9 +13,10 @@ class logging_log_metric_filter_and_alert_for_compute_configuration_changes_enab
|
||||
):
|
||||
def execute(self) -> Check_Report_GCP:
|
||||
findings = []
|
||||
metric_filter = 'protoPayload.serviceName="compute.googleapis.com"'
|
||||
projects_with_metric = set()
|
||||
for metric in logging_client.metrics:
|
||||
if 'protoPayload.serviceName="compute.googleapis.com"' in metric.filter:
|
||||
if metric_filter in metric.filter:
|
||||
report = Check_Report_GCP(
|
||||
metadata=self.metadata(),
|
||||
resource=metric,
|
||||
@@ -30,6 +34,9 @@ class logging_log_metric_filter_and_alert_for_compute_configuration_changes_enab
|
||||
break
|
||||
findings.append(report)
|
||||
|
||||
centrally_covered = get_projects_covered_by_aggregated_metric(
|
||||
logging_client, monitoring_client, metric_filter
|
||||
)
|
||||
for project in logging_client.project_ids:
|
||||
if project not in projects_with_metric:
|
||||
report = Check_Report_GCP(
|
||||
@@ -43,8 +50,12 @@ class logging_log_metric_filter_and_alert_for_compute_configuration_changes_enab
|
||||
else "GCP Project"
|
||||
),
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There are no log metric filters or alerts associated for Compute Engine configuration changes in project {project}."
|
||||
if project in centrally_covered:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Log metric filter {centrally_covered[project]} found with an alert, covering project {project} via an organization-level aggregated sink."
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There are no log metric filters or alerts associated for Compute Engine configuration changes in project {project}."
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
|
||||
+14
-6
@@ -1,5 +1,8 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||
from prowler.providers.gcp.services.logging.logging_client import logging_client
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
get_projects_covered_by_aggregated_metric,
|
||||
)
|
||||
from prowler.providers.gcp.services.monitoring.monitoring_client import (
|
||||
monitoring_client,
|
||||
)
|
||||
@@ -8,12 +11,10 @@ from prowler.providers.gcp.services.monitoring.monitoring_client import (
|
||||
class logging_log_metric_filter_and_alert_for_custom_role_changes_enabled(Check):
|
||||
def execute(self) -> Check_Report_GCP:
|
||||
findings = []
|
||||
metric_filter = 'resource.type="iam_role" AND (protoPayload.methodName="google.iam.admin.v1.CreateRole" OR protoPayload.methodName="google.iam.admin.v1.DeleteRole" OR protoPayload.methodName="google.iam.admin.v1.UpdateRole")'
|
||||
projects_with_metric = set()
|
||||
for metric in logging_client.metrics:
|
||||
if (
|
||||
'resource.type="iam_role" AND (protoPayload.methodName="google.iam.admin.v1.CreateRole" OR protoPayload.methodName="google.iam.admin.v1.DeleteRole" OR protoPayload.methodName="google.iam.admin.v1.UpdateRole")'
|
||||
in metric.filter
|
||||
):
|
||||
if metric_filter in metric.filter:
|
||||
report = Check_Report_GCP(
|
||||
metadata=self.metadata(),
|
||||
resource=metric,
|
||||
@@ -31,6 +32,9 @@ class logging_log_metric_filter_and_alert_for_custom_role_changes_enabled(Check)
|
||||
break
|
||||
findings.append(report)
|
||||
|
||||
centrally_covered = get_projects_covered_by_aggregated_metric(
|
||||
logging_client, monitoring_client, metric_filter
|
||||
)
|
||||
for project in logging_client.project_ids:
|
||||
if project not in projects_with_metric:
|
||||
report = Check_Report_GCP(
|
||||
@@ -44,8 +48,12 @@ class logging_log_metric_filter_and_alert_for_custom_role_changes_enabled(Check)
|
||||
else "GCP Project"
|
||||
),
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There are no log metric filters or alerts associated in project {project}."
|
||||
if project in centrally_covered:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Log metric filter {centrally_covered[project]} found with an alert, covering project {project} via an organization-level aggregated sink."
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There are no log metric filters or alerts associated in project {project}."
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
|
||||
+14
-6
@@ -1,5 +1,8 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||
from prowler.providers.gcp.services.logging.logging_client import logging_client
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
get_projects_covered_by_aggregated_metric,
|
||||
)
|
||||
from prowler.providers.gcp.services.monitoring.monitoring_client import (
|
||||
monitoring_client,
|
||||
)
|
||||
@@ -8,12 +11,10 @@ from prowler.providers.gcp.services.monitoring.monitoring_client import (
|
||||
class logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled(Check):
|
||||
def execute(self) -> Check_Report_GCP:
|
||||
findings = []
|
||||
metric_filter = '(protoPayload.serviceName="cloudresourcemanager.googleapis.com") AND (ProjectOwnership OR projectOwnerInvitee) OR (protoPayload.serviceData.policyDelta.bindingDeltas.action="REMOVE" AND protoPayload.serviceData.policyDelta.bindingDeltas.role="roles/owner") OR (protoPayload.serviceData.policyDelta.bindingDeltas.action="ADD" AND protoPayload.serviceData.policyDelta.bindingDeltas.role="roles/owner")'
|
||||
projects_with_metric = set()
|
||||
for metric in logging_client.metrics:
|
||||
if (
|
||||
'(protoPayload.serviceName="cloudresourcemanager.googleapis.com") AND (ProjectOwnership OR projectOwnerInvitee) OR (protoPayload.serviceData.policyDelta.bindingDeltas.action="REMOVE" AND protoPayload.serviceData.policyDelta.bindingDeltas.role="roles/owner") OR (protoPayload.serviceData.policyDelta.bindingDeltas.action="ADD" AND protoPayload.serviceData.policyDelta.bindingDeltas.role="roles/owner")'
|
||||
in metric.filter
|
||||
):
|
||||
if metric_filter in metric.filter:
|
||||
metric_name = getattr(metric, "name", None) or "unknown"
|
||||
report = Check_Report_GCP(
|
||||
metadata=self.metadata(),
|
||||
@@ -36,6 +37,9 @@ class logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled(
|
||||
break
|
||||
findings.append(report)
|
||||
|
||||
centrally_covered = get_projects_covered_by_aggregated_metric(
|
||||
logging_client, monitoring_client, metric_filter
|
||||
)
|
||||
for project in logging_client.project_ids:
|
||||
if project not in projects_with_metric:
|
||||
project_obj = logging_client.projects.get(project)
|
||||
@@ -47,8 +51,12 @@ class logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled(
|
||||
location=logging_client.region,
|
||||
resource_name=(getattr(project_obj, "name", None) or "GCP Project"),
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There are no log metric filters or alerts associated in project {project}."
|
||||
if project in centrally_covered:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Log metric filter {centrally_covered[project]} found with an alert, covering project {project} via an organization-level aggregated sink."
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There are no log metric filters or alerts associated in project {project}."
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
|
||||
+14
-3
@@ -1,5 +1,8 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||
from prowler.providers.gcp.services.logging.logging_client import logging_client
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
get_projects_covered_by_aggregated_metric,
|
||||
)
|
||||
from prowler.providers.gcp.services.monitoring.monitoring_client import (
|
||||
monitoring_client,
|
||||
)
|
||||
@@ -10,9 +13,10 @@ class logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes
|
||||
):
|
||||
def execute(self) -> Check_Report_GCP:
|
||||
findings = []
|
||||
metric_filter = 'protoPayload.methodName="cloudsql.instances.update"'
|
||||
projects_with_metric = set()
|
||||
for metric in logging_client.metrics:
|
||||
if 'protoPayload.methodName="cloudsql.instances.update"' in metric.filter:
|
||||
if metric_filter in metric.filter:
|
||||
report = Check_Report_GCP(
|
||||
metadata=self.metadata(),
|
||||
resource=metric,
|
||||
@@ -30,6 +34,9 @@ class logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes
|
||||
break
|
||||
findings.append(report)
|
||||
|
||||
centrally_covered = get_projects_covered_by_aggregated_metric(
|
||||
logging_client, monitoring_client, metric_filter
|
||||
)
|
||||
for project in logging_client.project_ids:
|
||||
if project not in projects_with_metric:
|
||||
report = Check_Report_GCP(
|
||||
@@ -43,8 +50,12 @@ class logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes
|
||||
else "GCP Project"
|
||||
),
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There are no log metric filters or alerts associated in project {project}."
|
||||
if project in centrally_covered:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Log metric filter {centrally_covered[project]} found with an alert, covering project {project} via an organization-level aggregated sink."
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There are no log metric filters or alerts associated in project {project}."
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
|
||||
+14
-6
@@ -1,5 +1,8 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||
from prowler.providers.gcp.services.logging.logging_client import logging_client
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
get_projects_covered_by_aggregated_metric,
|
||||
)
|
||||
from prowler.providers.gcp.services.monitoring.monitoring_client import (
|
||||
monitoring_client,
|
||||
)
|
||||
@@ -8,12 +11,10 @@ from prowler.providers.gcp.services.monitoring.monitoring_client import (
|
||||
class logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled(Check):
|
||||
def execute(self) -> Check_Report_GCP:
|
||||
findings = []
|
||||
metric_filter = 'resource.type="gce_firewall_rule" AND (protoPayload.methodName:"compute.firewalls.patch" OR protoPayload.methodName:"compute.firewalls.insert" OR protoPayload.methodName:"compute.firewalls.delete")'
|
||||
projects_with_metric = set()
|
||||
for metric in logging_client.metrics:
|
||||
if (
|
||||
'resource.type="gce_firewall_rule" AND (protoPayload.methodName:"compute.firewalls.patch" OR protoPayload.methodName:"compute.firewalls.insert" OR protoPayload.methodName:"compute.firewalls.delete")'
|
||||
in metric.filter
|
||||
):
|
||||
if metric_filter in metric.filter:
|
||||
report = Check_Report_GCP(
|
||||
metadata=self.metadata(),
|
||||
resource=metric,
|
||||
@@ -31,6 +32,9 @@ class logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled(
|
||||
break
|
||||
findings.append(report)
|
||||
|
||||
centrally_covered = get_projects_covered_by_aggregated_metric(
|
||||
logging_client, monitoring_client, metric_filter
|
||||
)
|
||||
for project in logging_client.project_ids:
|
||||
if project not in projects_with_metric:
|
||||
report = Check_Report_GCP(
|
||||
@@ -44,8 +48,12 @@ class logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled(
|
||||
else "GCP Project"
|
||||
),
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There are no log metric filters or alerts associated in project {project}."
|
||||
if project in centrally_covered:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Log metric filter {centrally_covered[project]} found with an alert, covering project {project} via an organization-level aggregated sink."
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There are no log metric filters or alerts associated in project {project}."
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
|
||||
+14
-6
@@ -1,5 +1,8 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||
from prowler.providers.gcp.services.logging.logging_client import logging_client
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
get_projects_covered_by_aggregated_metric,
|
||||
)
|
||||
from prowler.providers.gcp.services.monitoring.monitoring_client import (
|
||||
monitoring_client,
|
||||
)
|
||||
@@ -8,12 +11,10 @@ from prowler.providers.gcp.services.monitoring.monitoring_client import (
|
||||
class logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled(Check):
|
||||
def execute(self) -> Check_Report_GCP:
|
||||
findings = []
|
||||
metric_filter = 'resource.type="gce_network" AND (protoPayload.methodName:"compute.networks.insert" OR protoPayload.methodName:"compute.networks.patch" OR protoPayload.methodName:"compute.networks.delete" OR protoPayload.methodName:"compute.networks.removePeering" OR protoPayload.methodName:"compute.networks.addPeering")'
|
||||
projects_with_metric = set()
|
||||
for metric in logging_client.metrics:
|
||||
if (
|
||||
'resource.type="gce_network" AND (protoPayload.methodName:"compute.networks.insert" OR protoPayload.methodName:"compute.networks.patch" OR protoPayload.methodName:"compute.networks.delete" OR protoPayload.methodName:"compute.networks.removePeering" OR protoPayload.methodName:"compute.networks.addPeering")'
|
||||
in metric.filter
|
||||
):
|
||||
if metric_filter in metric.filter:
|
||||
report = Check_Report_GCP(
|
||||
metadata=self.metadata(),
|
||||
resource=metric,
|
||||
@@ -31,6 +32,9 @@ class logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled(Check)
|
||||
break
|
||||
findings.append(report)
|
||||
|
||||
centrally_covered = get_projects_covered_by_aggregated_metric(
|
||||
logging_client, monitoring_client, metric_filter
|
||||
)
|
||||
for project in logging_client.project_ids:
|
||||
if project not in projects_with_metric:
|
||||
report = Check_Report_GCP(
|
||||
@@ -44,8 +48,12 @@ class logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled(Check)
|
||||
else "GCP Project"
|
||||
),
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There are no log metric filters or alerts associated in project {project}."
|
||||
if project in centrally_covered:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Log metric filter {centrally_covered[project]} found with an alert, covering project {project} via an organization-level aggregated sink."
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There are no log metric filters or alerts associated in project {project}."
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
|
||||
+14
-6
@@ -1,5 +1,8 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_GCP
|
||||
from prowler.providers.gcp.services.logging.logging_client import logging_client
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
get_projects_covered_by_aggregated_metric,
|
||||
)
|
||||
from prowler.providers.gcp.services.monitoring.monitoring_client import (
|
||||
monitoring_client,
|
||||
)
|
||||
@@ -8,12 +11,10 @@ from prowler.providers.gcp.services.monitoring.monitoring_client import (
|
||||
class logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled(Check):
|
||||
def execute(self) -> Check_Report_GCP:
|
||||
findings = []
|
||||
metric_filter = 'resource.type="gce_route" AND (protoPayload.methodName:"compute.routes.delete" OR protoPayload.methodName:"compute.routes.insert")'
|
||||
projects_with_metric = set()
|
||||
for metric in logging_client.metrics:
|
||||
if (
|
||||
'resource.type="gce_route" AND (protoPayload.methodName:"compute.routes.delete" OR protoPayload.methodName:"compute.routes.insert")'
|
||||
in metric.filter
|
||||
):
|
||||
if metric_filter in metric.filter:
|
||||
report = Check_Report_GCP(
|
||||
metadata=self.metadata(),
|
||||
resource=metric,
|
||||
@@ -31,6 +32,9 @@ class logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled(
|
||||
break
|
||||
findings.append(report)
|
||||
|
||||
centrally_covered = get_projects_covered_by_aggregated_metric(
|
||||
logging_client, monitoring_client, metric_filter
|
||||
)
|
||||
for project in logging_client.project_ids:
|
||||
if project not in projects_with_metric:
|
||||
report = Check_Report_GCP(
|
||||
@@ -44,8 +48,12 @@ class logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled(
|
||||
else "GCP Project"
|
||||
),
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There are no log metric filters or alerts associated in project {project}."
|
||||
if project in centrally_covered:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Log metric filter {centrally_covered[project]} found with an alert, covering project {project} via an organization-level aggregated sink."
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"There are no log metric filters or alerts associated in project {project}."
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
|
||||
@@ -90,6 +90,7 @@ class Logging(GCPService):
|
||||
type=metric["metricDescriptor"]["type"],
|
||||
filter=metric["filter"],
|
||||
project_id=project_id,
|
||||
bucket_name=metric.get("bucketName", ""),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -117,3 +118,59 @@ class Metric(BaseModel):
|
||||
type: str
|
||||
filter: str
|
||||
project_id: str
|
||||
bucket_name: str = ""
|
||||
|
||||
|
||||
def get_projects_covered_by_aggregated_metric(
|
||||
logging_client, monitoring_client, metric_filter
|
||||
):
|
||||
"""Return {project_id: metric_name} for scanned projects whose logs are routed,
|
||||
via an organization-level sink with includeChildren=True, to a bucket that holds
|
||||
a bucket-scoped log metric matching ``metric_filter`` that has an alert policy.
|
||||
|
||||
The CIS GCP logging-metric checks are written per-project, but a common (and
|
||||
recommended) topology centralizes monitoring: an org-level aggregated sink ships
|
||||
every child project's logs into one bucket, where a single bucket-scoped metric
|
||||
+ alert covers them all. Without crediting that, those child projects are falsely
|
||||
failed. Mirrors the org-sink handling already in ``logging_sink_created`` (#11355).
|
||||
"""
|
||||
# Buckets that hold a matching, alerted, bucket-scoped metric -> metric name.
|
||||
bucket_to_metric = {}
|
||||
for metric in logging_client.metrics:
|
||||
if not getattr(metric, "bucket_name", ""):
|
||||
continue
|
||||
if metric_filter not in metric.filter:
|
||||
continue
|
||||
if any(
|
||||
metric.name in policy_filter
|
||||
for alert_policy in monitoring_client.alert_policies
|
||||
for policy_filter in alert_policy.filters
|
||||
):
|
||||
bucket_to_metric[metric.bucket_name] = metric.name
|
||||
if not bucket_to_metric:
|
||||
return {}
|
||||
|
||||
# Org resources whose includeChildren sink targets one of those buckets.
|
||||
org_to_metric = {}
|
||||
for sink in logging_client.sinks:
|
||||
if not getattr(sink, "include_children", False):
|
||||
continue
|
||||
if getattr(sink, "filter", "all") != "all":
|
||||
continue
|
||||
for bucket, metric_name in bucket_to_metric.items():
|
||||
# sink.destination e.g. "logging.googleapis.com/projects/.../buckets/X";
|
||||
# metric.bucket_name e.g. "projects/.../buckets/X".
|
||||
if sink.destination.endswith(bucket):
|
||||
org_to_metric[sink.project_id] = metric_name
|
||||
break
|
||||
if not org_to_metric:
|
||||
return {}
|
||||
|
||||
# Scanned projects sitting under a covering organization.
|
||||
covered = {}
|
||||
for project_id in logging_client.project_ids:
|
||||
project = logging_client.projects.get(project_id)
|
||||
organization = getattr(project, "organization", None) if project else None
|
||||
if organization and f"organizations/{organization.id}" in org_to_metric:
|
||||
covered[project_id] = org_to_metric[f"organizations/{organization.id}"]
|
||||
return covered
|
||||
|
||||
+173
@@ -259,3 +259,176 @@ class Test_logging_log_metric_filter_and_alert_for_audit_configuration_changes_e
|
||||
assert result[0].resource_name == "metric_name"
|
||||
assert result[0].project_id == GCP_PROJECT_ID
|
||||
assert result[0].location == GCP_EU1_LOCATION
|
||||
|
||||
def test_project_centrally_covered_via_org_aggregated_sink(self):
|
||||
"""A child project with NO local metric, but whose org has an aggregated
|
||||
sink (includeChildren=True) routing its logs to a central bucket that has
|
||||
a bucket-scoped metric + alert, should PASS (covered centrally) instead of
|
||||
being falsely failed."""
|
||||
logging_client = MagicMock()
|
||||
monitoring_client = MagicMock()
|
||||
org_id = "111222333"
|
||||
central_bucket = (
|
||||
"projects/central-logging-project/locations/eu/buckets/central-bucket"
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled.logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled.logging_client",
|
||||
new=logging_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled.logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled.monitoring_client",
|
||||
new=monitoring_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.gcp.models import GCPOrganization
|
||||
from prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled.logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled import (
|
||||
logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled,
|
||||
)
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
Metric,
|
||||
Sink,
|
||||
)
|
||||
from prowler.providers.gcp.services.monitoring.monitoring_service import (
|
||||
AlertPolicy,
|
||||
)
|
||||
|
||||
logging_client.region = GCP_EU1_LOCATION
|
||||
logging_client.project_ids = [GCP_PROJECT_ID, "central-logging-project"]
|
||||
logging_client.projects = {
|
||||
GCP_PROJECT_ID: GCPProject(
|
||||
id=GCP_PROJECT_ID,
|
||||
number="123456789012",
|
||||
name="child",
|
||||
labels={},
|
||||
lifecycle_state="ACTIVE",
|
||||
organization=GCPOrganization(
|
||||
id=org_id, name=f"organizations/{org_id}"
|
||||
),
|
||||
)
|
||||
}
|
||||
# Bucket-scoped central metric, in the scanned logging project.
|
||||
logging_client.metrics = [
|
||||
Metric(
|
||||
name="central-audit-config-metric",
|
||||
type="logging.googleapis.com/user/central-audit-config-metric",
|
||||
filter='protoPayload.methodName="SetIamPolicy" AND protoPayload.serviceData.policyDelta.auditConfigDeltas:*',
|
||||
project_id="central-logging-project",
|
||||
bucket_name=central_bucket,
|
||||
)
|
||||
]
|
||||
# Org-level aggregated sink routing the child's logs to that bucket.
|
||||
logging_client.sinks = [
|
||||
Sink(
|
||||
name="org-aggregated-sink",
|
||||
destination=f"logging.googleapis.com/{central_bucket}",
|
||||
filter="all",
|
||||
project_id=f"organizations/{org_id}",
|
||||
include_children=True,
|
||||
)
|
||||
]
|
||||
monitoring_client.alert_policies = [
|
||||
AlertPolicy(
|
||||
name="projects/central-logging-project/alertPolicies/ap",
|
||||
display_name="central-alert",
|
||||
enabled=True,
|
||||
filters=[
|
||||
'metric.type = "logging.googleapis.com/user/central-audit-config-metric"'
|
||||
],
|
||||
project_id="central-logging-project",
|
||||
)
|
||||
]
|
||||
|
||||
check = (
|
||||
logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled()
|
||||
)
|
||||
result = check.execute()
|
||||
|
||||
assert any(
|
||||
r.project_id == GCP_PROJECT_ID
|
||||
and r.status == "PASS"
|
||||
and "aggregated sink" in r.status_extended
|
||||
for r in result
|
||||
), [(r.project_id, r.status, r.status_extended) for r in result]
|
||||
|
||||
def test_aggregated_sink_metric_without_alert_still_fails(self):
|
||||
"""Guard: an org aggregated sink + a bucket-scoped metric matching the filter
|
||||
but with NO alert must NOT credit the child project — it should still FAIL."""
|
||||
logging_client = MagicMock()
|
||||
monitoring_client = MagicMock()
|
||||
org_id = "111222333"
|
||||
central_bucket = (
|
||||
"projects/central-logging-project/locations/eu/buckets/central-bucket"
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled.logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled.logging_client",
|
||||
new=logging_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled.logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled.monitoring_client",
|
||||
new=monitoring_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.gcp.models import GCPOrganization
|
||||
from prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled.logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled import (
|
||||
logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled,
|
||||
)
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
Metric,
|
||||
Sink,
|
||||
)
|
||||
|
||||
logging_client.region = GCP_EU1_LOCATION
|
||||
logging_client.project_ids = [GCP_PROJECT_ID, "central-logging-project"]
|
||||
logging_client.projects = {
|
||||
GCP_PROJECT_ID: GCPProject(
|
||||
id=GCP_PROJECT_ID,
|
||||
number="123456789012",
|
||||
name="child",
|
||||
labels={},
|
||||
lifecycle_state="ACTIVE",
|
||||
organization=GCPOrganization(
|
||||
id=org_id, name=f"organizations/{org_id}"
|
||||
),
|
||||
)
|
||||
}
|
||||
logging_client.metrics = [
|
||||
Metric(
|
||||
name="central-metric",
|
||||
type="logging.googleapis.com/user/central-metric",
|
||||
filter='protoPayload.methodName="SetIamPolicy" AND protoPayload.serviceData.policyDelta.auditConfigDeltas:*',
|
||||
project_id="central-logging-project",
|
||||
bucket_name=central_bucket,
|
||||
)
|
||||
]
|
||||
logging_client.sinks = [
|
||||
Sink(
|
||||
name="org-aggregated-sink",
|
||||
destination=f"logging.googleapis.com/{central_bucket}",
|
||||
filter="all",
|
||||
project_id=f"organizations/{org_id}",
|
||||
include_children=True,
|
||||
)
|
||||
]
|
||||
monitoring_client.alert_policies = [] # no alert -> must NOT credit
|
||||
|
||||
check = (
|
||||
logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled()
|
||||
)
|
||||
result = check.execute()
|
||||
|
||||
child = [r for r in result if r.project_id == GCP_PROJECT_ID]
|
||||
assert child and all(r.status == "FAIL" for r in child), [
|
||||
(r.project_id, r.status) for r in result
|
||||
]
|
||||
|
||||
+170
@@ -397,3 +397,173 @@ class Test_logging_log_metric_filter_and_alert_for_bucket_permission_changes_ena
|
||||
assert result[0].resource_name == "GCP Project"
|
||||
assert result[0].project_id == GCP_PROJECT_ID
|
||||
assert result[0].location == GCP_EU1_LOCATION
|
||||
|
||||
def test_project_centrally_covered_via_org_aggregated_sink(self):
|
||||
"""A child project with NO local metric, but whose org has an aggregated
|
||||
sink (includeChildren=True) routing its logs to a central bucket that has
|
||||
a bucket-scoped metric + alert, should PASS (covered centrally)."""
|
||||
logging_client = MagicMock()
|
||||
monitoring_client = MagicMock()
|
||||
org_id = "111222333"
|
||||
central_bucket = (
|
||||
"projects/central-logging-project/locations/eu/buckets/central-bucket"
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled.logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled.logging_client",
|
||||
new=logging_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled.logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled.monitoring_client",
|
||||
new=monitoring_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.gcp.models import GCPOrganization, GCPProject
|
||||
from prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled.logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled import (
|
||||
logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled,
|
||||
)
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
Metric,
|
||||
Sink,
|
||||
)
|
||||
from prowler.providers.gcp.services.monitoring.monitoring_service import (
|
||||
AlertPolicy,
|
||||
)
|
||||
|
||||
logging_client.region = GCP_EU1_LOCATION
|
||||
logging_client.project_ids = [GCP_PROJECT_ID, "central-logging-project"]
|
||||
logging_client.projects = {
|
||||
GCP_PROJECT_ID: GCPProject(
|
||||
id=GCP_PROJECT_ID,
|
||||
number="123456789012",
|
||||
name="child",
|
||||
labels={},
|
||||
lifecycle_state="ACTIVE",
|
||||
organization=GCPOrganization(
|
||||
id=org_id, name=f"organizations/{org_id}"
|
||||
),
|
||||
)
|
||||
}
|
||||
logging_client.metrics = [
|
||||
Metric(
|
||||
name="central-metric",
|
||||
type="logging.googleapis.com/user/central-metric",
|
||||
filter='resource.type="gcs_bucket" AND protoPayload.methodName="storage.setIamPermissions"',
|
||||
project_id="central-logging-project",
|
||||
bucket_name=central_bucket,
|
||||
)
|
||||
]
|
||||
logging_client.sinks = [
|
||||
Sink(
|
||||
name="org-aggregated-sink",
|
||||
destination=f"logging.googleapis.com/{central_bucket}",
|
||||
filter="all",
|
||||
project_id=f"organizations/{org_id}",
|
||||
include_children=True,
|
||||
)
|
||||
]
|
||||
monitoring_client.alert_policies = [
|
||||
AlertPolicy(
|
||||
name="projects/central-logging-project/alertPolicies/ap",
|
||||
display_name="central-alert",
|
||||
enabled=True,
|
||||
filters=[
|
||||
'metric.type = "logging.googleapis.com/user/central-metric"'
|
||||
],
|
||||
project_id="central-logging-project",
|
||||
)
|
||||
]
|
||||
|
||||
check = (
|
||||
logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled()
|
||||
)
|
||||
result = check.execute()
|
||||
|
||||
assert any(
|
||||
r.project_id == GCP_PROJECT_ID
|
||||
and r.status == "PASS"
|
||||
and "aggregated sink" in r.status_extended
|
||||
for r in result
|
||||
), [(r.project_id, r.status, r.status_extended) for r in result]
|
||||
|
||||
def test_aggregated_sink_metric_without_alert_still_fails(self):
|
||||
"""Guard: an org aggregated sink + a bucket-scoped metric matching the filter
|
||||
but with NO alert must NOT credit the child project — it should still FAIL."""
|
||||
logging_client = MagicMock()
|
||||
monitoring_client = MagicMock()
|
||||
org_id = "111222333"
|
||||
central_bucket = (
|
||||
"projects/central-logging-project/locations/eu/buckets/central-bucket"
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled.logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled.logging_client",
|
||||
new=logging_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled.logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled.monitoring_client",
|
||||
new=monitoring_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.gcp.models import GCPOrganization, GCPProject
|
||||
from prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled.logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled import (
|
||||
logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled,
|
||||
)
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
Metric,
|
||||
Sink,
|
||||
)
|
||||
|
||||
logging_client.region = GCP_EU1_LOCATION
|
||||
logging_client.project_ids = [GCP_PROJECT_ID, "central-logging-project"]
|
||||
logging_client.projects = {
|
||||
GCP_PROJECT_ID: GCPProject(
|
||||
id=GCP_PROJECT_ID,
|
||||
number="123456789012",
|
||||
name="child",
|
||||
labels={},
|
||||
lifecycle_state="ACTIVE",
|
||||
organization=GCPOrganization(
|
||||
id=org_id, name=f"organizations/{org_id}"
|
||||
),
|
||||
)
|
||||
}
|
||||
logging_client.metrics = [
|
||||
Metric(
|
||||
name="central-metric",
|
||||
type="logging.googleapis.com/user/central-metric",
|
||||
filter='resource.type="gcs_bucket" AND protoPayload.methodName="storage.setIamPermissions"',
|
||||
project_id="central-logging-project",
|
||||
bucket_name=central_bucket,
|
||||
)
|
||||
]
|
||||
logging_client.sinks = [
|
||||
Sink(
|
||||
name="org-aggregated-sink",
|
||||
destination=f"logging.googleapis.com/{central_bucket}",
|
||||
filter="all",
|
||||
project_id=f"organizations/{org_id}",
|
||||
include_children=True,
|
||||
)
|
||||
]
|
||||
monitoring_client.alert_policies = [] # no alert -> must NOT credit
|
||||
|
||||
check = (
|
||||
logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled()
|
||||
)
|
||||
result = check.execute()
|
||||
|
||||
child = [r for r in result if r.project_id == GCP_PROJECT_ID]
|
||||
assert child and all(r.status == "FAIL" for r in child), [
|
||||
(r.project_id, r.status) for r in result
|
||||
]
|
||||
|
||||
+170
@@ -346,3 +346,173 @@ class Test_logging_log_metric_filter_and_alert_for_compute_configuration_changes
|
||||
fail_result = [r for r in result if r.status == "FAIL"][0]
|
||||
assert fail_result.project_id == project_id_2
|
||||
assert "no log metric filters" in fail_result.status_extended
|
||||
|
||||
def test_project_centrally_covered_via_org_aggregated_sink(self):
|
||||
"""A child project with NO local metric, but whose org has an aggregated
|
||||
sink (includeChildren=True) routing its logs to a central bucket that has
|
||||
a bucket-scoped metric + alert, should PASS (covered centrally)."""
|
||||
logging_client = MagicMock()
|
||||
monitoring_client = MagicMock()
|
||||
org_id = "111222333"
|
||||
central_bucket = (
|
||||
"projects/central-logging-project/locations/eu/buckets/central-bucket"
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled.logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled.logging_client",
|
||||
new=logging_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled.logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled.monitoring_client",
|
||||
new=monitoring_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.gcp.models import GCPOrganization, GCPProject
|
||||
from prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled.logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled import (
|
||||
logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled,
|
||||
)
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
Metric,
|
||||
Sink,
|
||||
)
|
||||
from prowler.providers.gcp.services.monitoring.monitoring_service import (
|
||||
AlertPolicy,
|
||||
)
|
||||
|
||||
logging_client.region = GCP_EU1_LOCATION
|
||||
logging_client.project_ids = [GCP_PROJECT_ID, "central-logging-project"]
|
||||
logging_client.projects = {
|
||||
GCP_PROJECT_ID: GCPProject(
|
||||
id=GCP_PROJECT_ID,
|
||||
number="123456789012",
|
||||
name="child",
|
||||
labels={},
|
||||
lifecycle_state="ACTIVE",
|
||||
organization=GCPOrganization(
|
||||
id=org_id, name=f"organizations/{org_id}"
|
||||
),
|
||||
)
|
||||
}
|
||||
logging_client.metrics = [
|
||||
Metric(
|
||||
name="central-metric",
|
||||
type="logging.googleapis.com/user/central-metric",
|
||||
filter='protoPayload.serviceName="compute.googleapis.com"',
|
||||
project_id="central-logging-project",
|
||||
bucket_name=central_bucket,
|
||||
)
|
||||
]
|
||||
logging_client.sinks = [
|
||||
Sink(
|
||||
name="org-aggregated-sink",
|
||||
destination=f"logging.googleapis.com/{central_bucket}",
|
||||
filter="all",
|
||||
project_id=f"organizations/{org_id}",
|
||||
include_children=True,
|
||||
)
|
||||
]
|
||||
monitoring_client.alert_policies = [
|
||||
AlertPolicy(
|
||||
name="projects/central-logging-project/alertPolicies/ap",
|
||||
display_name="central-alert",
|
||||
enabled=True,
|
||||
filters=[
|
||||
'metric.type = "logging.googleapis.com/user/central-metric"'
|
||||
],
|
||||
project_id="central-logging-project",
|
||||
)
|
||||
]
|
||||
|
||||
check = (
|
||||
logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled()
|
||||
)
|
||||
result = check.execute()
|
||||
|
||||
assert any(
|
||||
r.project_id == GCP_PROJECT_ID
|
||||
and r.status == "PASS"
|
||||
and "aggregated sink" in r.status_extended
|
||||
for r in result
|
||||
), [(r.project_id, r.status, r.status_extended) for r in result]
|
||||
|
||||
def test_aggregated_sink_metric_without_alert_still_fails(self):
|
||||
"""Guard: an org aggregated sink + a bucket-scoped metric matching the filter
|
||||
but with NO alert must NOT credit the child project — it should still FAIL."""
|
||||
logging_client = MagicMock()
|
||||
monitoring_client = MagicMock()
|
||||
org_id = "111222333"
|
||||
central_bucket = (
|
||||
"projects/central-logging-project/locations/eu/buckets/central-bucket"
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled.logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled.logging_client",
|
||||
new=logging_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled.logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled.monitoring_client",
|
||||
new=monitoring_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.gcp.models import GCPOrganization, GCPProject
|
||||
from prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled.logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled import (
|
||||
logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled,
|
||||
)
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
Metric,
|
||||
Sink,
|
||||
)
|
||||
|
||||
logging_client.region = GCP_EU1_LOCATION
|
||||
logging_client.project_ids = [GCP_PROJECT_ID, "central-logging-project"]
|
||||
logging_client.projects = {
|
||||
GCP_PROJECT_ID: GCPProject(
|
||||
id=GCP_PROJECT_ID,
|
||||
number="123456789012",
|
||||
name="child",
|
||||
labels={},
|
||||
lifecycle_state="ACTIVE",
|
||||
organization=GCPOrganization(
|
||||
id=org_id, name=f"organizations/{org_id}"
|
||||
),
|
||||
)
|
||||
}
|
||||
logging_client.metrics = [
|
||||
Metric(
|
||||
name="central-metric",
|
||||
type="logging.googleapis.com/user/central-metric",
|
||||
filter='protoPayload.serviceName="compute.googleapis.com"',
|
||||
project_id="central-logging-project",
|
||||
bucket_name=central_bucket,
|
||||
)
|
||||
]
|
||||
logging_client.sinks = [
|
||||
Sink(
|
||||
name="org-aggregated-sink",
|
||||
destination=f"logging.googleapis.com/{central_bucket}",
|
||||
filter="all",
|
||||
project_id=f"organizations/{org_id}",
|
||||
include_children=True,
|
||||
)
|
||||
]
|
||||
monitoring_client.alert_policies = [] # no alert -> must NOT credit
|
||||
|
||||
check = (
|
||||
logging_log_metric_filter_and_alert_for_compute_configuration_changes_enabled()
|
||||
)
|
||||
result = check.execute()
|
||||
|
||||
child = [r for r in result if r.project_id == GCP_PROJECT_ID]
|
||||
assert child and all(r.status == "FAIL" for r in child), [
|
||||
(r.project_id, r.status) for r in result
|
||||
]
|
||||
|
||||
+170
@@ -259,3 +259,173 @@ class Test_logging_log_metric_filter_and_alert_for_custom_role_changes_enabled:
|
||||
assert result[0].resource_name == "metric_name"
|
||||
assert result[0].project_id == GCP_PROJECT_ID
|
||||
assert result[0].location == GCP_EU1_LOCATION
|
||||
|
||||
def test_project_centrally_covered_via_org_aggregated_sink(self):
|
||||
"""A child project with NO local metric, but whose org has an aggregated
|
||||
sink (includeChildren=True) routing its logs to a central bucket that has
|
||||
a bucket-scoped metric + alert, should PASS (covered centrally)."""
|
||||
logging_client = MagicMock()
|
||||
monitoring_client = MagicMock()
|
||||
org_id = "111222333"
|
||||
central_bucket = (
|
||||
"projects/central-logging-project/locations/eu/buckets/central-bucket"
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_custom_role_changes_enabled.logging_log_metric_filter_and_alert_for_custom_role_changes_enabled.logging_client",
|
||||
new=logging_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_custom_role_changes_enabled.logging_log_metric_filter_and_alert_for_custom_role_changes_enabled.monitoring_client",
|
||||
new=monitoring_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.gcp.models import GCPOrganization, GCPProject
|
||||
from prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_custom_role_changes_enabled.logging_log_metric_filter_and_alert_for_custom_role_changes_enabled import (
|
||||
logging_log_metric_filter_and_alert_for_custom_role_changes_enabled,
|
||||
)
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
Metric,
|
||||
Sink,
|
||||
)
|
||||
from prowler.providers.gcp.services.monitoring.monitoring_service import (
|
||||
AlertPolicy,
|
||||
)
|
||||
|
||||
logging_client.region = GCP_EU1_LOCATION
|
||||
logging_client.project_ids = [GCP_PROJECT_ID, "central-logging-project"]
|
||||
logging_client.projects = {
|
||||
GCP_PROJECT_ID: GCPProject(
|
||||
id=GCP_PROJECT_ID,
|
||||
number="123456789012",
|
||||
name="child",
|
||||
labels={},
|
||||
lifecycle_state="ACTIVE",
|
||||
organization=GCPOrganization(
|
||||
id=org_id, name=f"organizations/{org_id}"
|
||||
),
|
||||
)
|
||||
}
|
||||
logging_client.metrics = [
|
||||
Metric(
|
||||
name="central-metric",
|
||||
type="logging.googleapis.com/user/central-metric",
|
||||
filter='resource.type="iam_role" AND (protoPayload.methodName="google.iam.admin.v1.CreateRole" OR protoPayload.methodName="google.iam.admin.v1.DeleteRole" OR protoPayload.methodName="google.iam.admin.v1.UpdateRole")',
|
||||
project_id="central-logging-project",
|
||||
bucket_name=central_bucket,
|
||||
)
|
||||
]
|
||||
logging_client.sinks = [
|
||||
Sink(
|
||||
name="org-aggregated-sink",
|
||||
destination=f"logging.googleapis.com/{central_bucket}",
|
||||
filter="all",
|
||||
project_id=f"organizations/{org_id}",
|
||||
include_children=True,
|
||||
)
|
||||
]
|
||||
monitoring_client.alert_policies = [
|
||||
AlertPolicy(
|
||||
name="projects/central-logging-project/alertPolicies/ap",
|
||||
display_name="central-alert",
|
||||
enabled=True,
|
||||
filters=[
|
||||
'metric.type = "logging.googleapis.com/user/central-metric"'
|
||||
],
|
||||
project_id="central-logging-project",
|
||||
)
|
||||
]
|
||||
|
||||
check = (
|
||||
logging_log_metric_filter_and_alert_for_custom_role_changes_enabled()
|
||||
)
|
||||
result = check.execute()
|
||||
|
||||
assert any(
|
||||
r.project_id == GCP_PROJECT_ID
|
||||
and r.status == "PASS"
|
||||
and "aggregated sink" in r.status_extended
|
||||
for r in result
|
||||
), [(r.project_id, r.status, r.status_extended) for r in result]
|
||||
|
||||
def test_aggregated_sink_metric_without_alert_still_fails(self):
|
||||
"""Guard: an org aggregated sink + a bucket-scoped metric matching the filter
|
||||
but with NO alert must NOT credit the child project — it should still FAIL."""
|
||||
logging_client = MagicMock()
|
||||
monitoring_client = MagicMock()
|
||||
org_id = "111222333"
|
||||
central_bucket = (
|
||||
"projects/central-logging-project/locations/eu/buckets/central-bucket"
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_custom_role_changes_enabled.logging_log_metric_filter_and_alert_for_custom_role_changes_enabled.logging_client",
|
||||
new=logging_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_custom_role_changes_enabled.logging_log_metric_filter_and_alert_for_custom_role_changes_enabled.monitoring_client",
|
||||
new=monitoring_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.gcp.models import GCPOrganization, GCPProject
|
||||
from prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_custom_role_changes_enabled.logging_log_metric_filter_and_alert_for_custom_role_changes_enabled import (
|
||||
logging_log_metric_filter_and_alert_for_custom_role_changes_enabled,
|
||||
)
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
Metric,
|
||||
Sink,
|
||||
)
|
||||
|
||||
logging_client.region = GCP_EU1_LOCATION
|
||||
logging_client.project_ids = [GCP_PROJECT_ID, "central-logging-project"]
|
||||
logging_client.projects = {
|
||||
GCP_PROJECT_ID: GCPProject(
|
||||
id=GCP_PROJECT_ID,
|
||||
number="123456789012",
|
||||
name="child",
|
||||
labels={},
|
||||
lifecycle_state="ACTIVE",
|
||||
organization=GCPOrganization(
|
||||
id=org_id, name=f"organizations/{org_id}"
|
||||
),
|
||||
)
|
||||
}
|
||||
logging_client.metrics = [
|
||||
Metric(
|
||||
name="central-metric",
|
||||
type="logging.googleapis.com/user/central-metric",
|
||||
filter='resource.type="iam_role" AND (protoPayload.methodName="google.iam.admin.v1.CreateRole" OR protoPayload.methodName="google.iam.admin.v1.DeleteRole" OR protoPayload.methodName="google.iam.admin.v1.UpdateRole")',
|
||||
project_id="central-logging-project",
|
||||
bucket_name=central_bucket,
|
||||
)
|
||||
]
|
||||
logging_client.sinks = [
|
||||
Sink(
|
||||
name="org-aggregated-sink",
|
||||
destination=f"logging.googleapis.com/{central_bucket}",
|
||||
filter="all",
|
||||
project_id=f"organizations/{org_id}",
|
||||
include_children=True,
|
||||
)
|
||||
]
|
||||
monitoring_client.alert_policies = [] # no alert -> must NOT credit
|
||||
|
||||
check = (
|
||||
logging_log_metric_filter_and_alert_for_custom_role_changes_enabled()
|
||||
)
|
||||
result = check.execute()
|
||||
|
||||
child = [r for r in result if r.project_id == GCP_PROJECT_ID]
|
||||
assert child and all(r.status == "FAIL" for r in child), [
|
||||
(r.project_id, r.status) for r in result
|
||||
]
|
||||
|
||||
+170
@@ -392,3 +392,173 @@ class Test_logging_log_metric_filter_and_alert_for_project_ownership_changes_ena
|
||||
assert result[0].resource_name == "GCP Project"
|
||||
assert result[0].project_id == GCP_PROJECT_ID
|
||||
assert result[0].location == GCP_EU1_LOCATION
|
||||
|
||||
def test_project_centrally_covered_via_org_aggregated_sink(self):
|
||||
"""A child project with NO local metric, but whose org has an aggregated
|
||||
sink (includeChildren=True) routing its logs to a central bucket that has
|
||||
a bucket-scoped metric + alert, should PASS (covered centrally)."""
|
||||
logging_client = MagicMock()
|
||||
monitoring_client = MagicMock()
|
||||
org_id = "111222333"
|
||||
central_bucket = (
|
||||
"projects/central-logging-project/locations/eu/buckets/central-bucket"
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled.logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled.logging_client",
|
||||
new=logging_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled.logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled.monitoring_client",
|
||||
new=monitoring_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.gcp.models import GCPOrganization, GCPProject
|
||||
from prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled.logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled import (
|
||||
logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled,
|
||||
)
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
Metric,
|
||||
Sink,
|
||||
)
|
||||
from prowler.providers.gcp.services.monitoring.monitoring_service import (
|
||||
AlertPolicy,
|
||||
)
|
||||
|
||||
logging_client.region = GCP_EU1_LOCATION
|
||||
logging_client.project_ids = [GCP_PROJECT_ID, "central-logging-project"]
|
||||
logging_client.projects = {
|
||||
GCP_PROJECT_ID: GCPProject(
|
||||
id=GCP_PROJECT_ID,
|
||||
number="123456789012",
|
||||
name="child",
|
||||
labels={},
|
||||
lifecycle_state="ACTIVE",
|
||||
organization=GCPOrganization(
|
||||
id=org_id, name=f"organizations/{org_id}"
|
||||
),
|
||||
)
|
||||
}
|
||||
logging_client.metrics = [
|
||||
Metric(
|
||||
name="central-metric",
|
||||
type="logging.googleapis.com/user/central-metric",
|
||||
filter='(protoPayload.serviceName="cloudresourcemanager.googleapis.com") AND (ProjectOwnership OR projectOwnerInvitee) OR (protoPayload.serviceData.policyDelta.bindingDeltas.action="REMOVE" AND protoPayload.serviceData.policyDelta.bindingDeltas.role="roles/owner") OR (protoPayload.serviceData.policyDelta.bindingDeltas.action="ADD" AND protoPayload.serviceData.policyDelta.bindingDeltas.role="roles/owner")',
|
||||
project_id="central-logging-project",
|
||||
bucket_name=central_bucket,
|
||||
)
|
||||
]
|
||||
logging_client.sinks = [
|
||||
Sink(
|
||||
name="org-aggregated-sink",
|
||||
destination=f"logging.googleapis.com/{central_bucket}",
|
||||
filter="all",
|
||||
project_id=f"organizations/{org_id}",
|
||||
include_children=True,
|
||||
)
|
||||
]
|
||||
monitoring_client.alert_policies = [
|
||||
AlertPolicy(
|
||||
name="projects/central-logging-project/alertPolicies/ap",
|
||||
display_name="central-alert",
|
||||
enabled=True,
|
||||
filters=[
|
||||
'metric.type = "logging.googleapis.com/user/central-metric"'
|
||||
],
|
||||
project_id="central-logging-project",
|
||||
)
|
||||
]
|
||||
|
||||
check = (
|
||||
logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled()
|
||||
)
|
||||
result = check.execute()
|
||||
|
||||
assert any(
|
||||
r.project_id == GCP_PROJECT_ID
|
||||
and r.status == "PASS"
|
||||
and "aggregated sink" in r.status_extended
|
||||
for r in result
|
||||
), [(r.project_id, r.status, r.status_extended) for r in result]
|
||||
|
||||
def test_aggregated_sink_metric_without_alert_still_fails(self):
|
||||
"""Guard: an org aggregated sink + a bucket-scoped metric matching the filter
|
||||
but with NO alert must NOT credit the child project — it should still FAIL."""
|
||||
logging_client = MagicMock()
|
||||
monitoring_client = MagicMock()
|
||||
org_id = "111222333"
|
||||
central_bucket = (
|
||||
"projects/central-logging-project/locations/eu/buckets/central-bucket"
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled.logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled.logging_client",
|
||||
new=logging_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled.logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled.monitoring_client",
|
||||
new=monitoring_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.gcp.models import GCPOrganization, GCPProject
|
||||
from prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled.logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled import (
|
||||
logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled,
|
||||
)
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
Metric,
|
||||
Sink,
|
||||
)
|
||||
|
||||
logging_client.region = GCP_EU1_LOCATION
|
||||
logging_client.project_ids = [GCP_PROJECT_ID, "central-logging-project"]
|
||||
logging_client.projects = {
|
||||
GCP_PROJECT_ID: GCPProject(
|
||||
id=GCP_PROJECT_ID,
|
||||
number="123456789012",
|
||||
name="child",
|
||||
labels={},
|
||||
lifecycle_state="ACTIVE",
|
||||
organization=GCPOrganization(
|
||||
id=org_id, name=f"organizations/{org_id}"
|
||||
),
|
||||
)
|
||||
}
|
||||
logging_client.metrics = [
|
||||
Metric(
|
||||
name="central-metric",
|
||||
type="logging.googleapis.com/user/central-metric",
|
||||
filter='(protoPayload.serviceName="cloudresourcemanager.googleapis.com") AND (ProjectOwnership OR projectOwnerInvitee) OR (protoPayload.serviceData.policyDelta.bindingDeltas.action="REMOVE" AND protoPayload.serviceData.policyDelta.bindingDeltas.role="roles/owner") OR (protoPayload.serviceData.policyDelta.bindingDeltas.action="ADD" AND protoPayload.serviceData.policyDelta.bindingDeltas.role="roles/owner")',
|
||||
project_id="central-logging-project",
|
||||
bucket_name=central_bucket,
|
||||
)
|
||||
]
|
||||
logging_client.sinks = [
|
||||
Sink(
|
||||
name="org-aggregated-sink",
|
||||
destination=f"logging.googleapis.com/{central_bucket}",
|
||||
filter="all",
|
||||
project_id=f"organizations/{org_id}",
|
||||
include_children=True,
|
||||
)
|
||||
]
|
||||
monitoring_client.alert_policies = [] # no alert -> must NOT credit
|
||||
|
||||
check = (
|
||||
logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled()
|
||||
)
|
||||
result = check.execute()
|
||||
|
||||
child = [r for r in result if r.project_id == GCP_PROJECT_ID]
|
||||
assert child and all(r.status == "FAIL" for r in child), [
|
||||
(r.project_id, r.status) for r in result
|
||||
]
|
||||
|
||||
+170
@@ -259,3 +259,173 @@ class Test_logging_log_metric_filter_and_alert_for_sql_instance_configuration_ch
|
||||
assert result[0].resource_name == "metric_name"
|
||||
assert result[0].project_id == GCP_PROJECT_ID
|
||||
assert result[0].location == GCP_EU1_LOCATION
|
||||
|
||||
def test_project_centrally_covered_via_org_aggregated_sink(self):
|
||||
"""A child project with NO local metric, but whose org has an aggregated
|
||||
sink (includeChildren=True) routing its logs to a central bucket that has
|
||||
a bucket-scoped metric + alert, should PASS (covered centrally)."""
|
||||
logging_client = MagicMock()
|
||||
monitoring_client = MagicMock()
|
||||
org_id = "111222333"
|
||||
central_bucket = (
|
||||
"projects/central-logging-project/locations/eu/buckets/central-bucket"
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled.logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled.logging_client",
|
||||
new=logging_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled.logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled.monitoring_client",
|
||||
new=monitoring_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.gcp.models import GCPOrganization, GCPProject
|
||||
from prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled.logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled import (
|
||||
logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled,
|
||||
)
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
Metric,
|
||||
Sink,
|
||||
)
|
||||
from prowler.providers.gcp.services.monitoring.monitoring_service import (
|
||||
AlertPolicy,
|
||||
)
|
||||
|
||||
logging_client.region = GCP_EU1_LOCATION
|
||||
logging_client.project_ids = [GCP_PROJECT_ID, "central-logging-project"]
|
||||
logging_client.projects = {
|
||||
GCP_PROJECT_ID: GCPProject(
|
||||
id=GCP_PROJECT_ID,
|
||||
number="123456789012",
|
||||
name="child",
|
||||
labels={},
|
||||
lifecycle_state="ACTIVE",
|
||||
organization=GCPOrganization(
|
||||
id=org_id, name=f"organizations/{org_id}"
|
||||
),
|
||||
)
|
||||
}
|
||||
logging_client.metrics = [
|
||||
Metric(
|
||||
name="central-metric",
|
||||
type="logging.googleapis.com/user/central-metric",
|
||||
filter='protoPayload.methodName="cloudsql.instances.update"',
|
||||
project_id="central-logging-project",
|
||||
bucket_name=central_bucket,
|
||||
)
|
||||
]
|
||||
logging_client.sinks = [
|
||||
Sink(
|
||||
name="org-aggregated-sink",
|
||||
destination=f"logging.googleapis.com/{central_bucket}",
|
||||
filter="all",
|
||||
project_id=f"organizations/{org_id}",
|
||||
include_children=True,
|
||||
)
|
||||
]
|
||||
monitoring_client.alert_policies = [
|
||||
AlertPolicy(
|
||||
name="projects/central-logging-project/alertPolicies/ap",
|
||||
display_name="central-alert",
|
||||
enabled=True,
|
||||
filters=[
|
||||
'metric.type = "logging.googleapis.com/user/central-metric"'
|
||||
],
|
||||
project_id="central-logging-project",
|
||||
)
|
||||
]
|
||||
|
||||
check = (
|
||||
logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled()
|
||||
)
|
||||
result = check.execute()
|
||||
|
||||
assert any(
|
||||
r.project_id == GCP_PROJECT_ID
|
||||
and r.status == "PASS"
|
||||
and "aggregated sink" in r.status_extended
|
||||
for r in result
|
||||
), [(r.project_id, r.status, r.status_extended) for r in result]
|
||||
|
||||
def test_aggregated_sink_metric_without_alert_still_fails(self):
|
||||
"""Guard: an org aggregated sink + a bucket-scoped metric matching the filter
|
||||
but with NO alert must NOT credit the child project — it should still FAIL."""
|
||||
logging_client = MagicMock()
|
||||
monitoring_client = MagicMock()
|
||||
org_id = "111222333"
|
||||
central_bucket = (
|
||||
"projects/central-logging-project/locations/eu/buckets/central-bucket"
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled.logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled.logging_client",
|
||||
new=logging_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled.logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled.monitoring_client",
|
||||
new=monitoring_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.gcp.models import GCPOrganization, GCPProject
|
||||
from prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled.logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled import (
|
||||
logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled,
|
||||
)
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
Metric,
|
||||
Sink,
|
||||
)
|
||||
|
||||
logging_client.region = GCP_EU1_LOCATION
|
||||
logging_client.project_ids = [GCP_PROJECT_ID, "central-logging-project"]
|
||||
logging_client.projects = {
|
||||
GCP_PROJECT_ID: GCPProject(
|
||||
id=GCP_PROJECT_ID,
|
||||
number="123456789012",
|
||||
name="child",
|
||||
labels={},
|
||||
lifecycle_state="ACTIVE",
|
||||
organization=GCPOrganization(
|
||||
id=org_id, name=f"organizations/{org_id}"
|
||||
),
|
||||
)
|
||||
}
|
||||
logging_client.metrics = [
|
||||
Metric(
|
||||
name="central-metric",
|
||||
type="logging.googleapis.com/user/central-metric",
|
||||
filter='protoPayload.methodName="cloudsql.instances.update"',
|
||||
project_id="central-logging-project",
|
||||
bucket_name=central_bucket,
|
||||
)
|
||||
]
|
||||
logging_client.sinks = [
|
||||
Sink(
|
||||
name="org-aggregated-sink",
|
||||
destination=f"logging.googleapis.com/{central_bucket}",
|
||||
filter="all",
|
||||
project_id=f"organizations/{org_id}",
|
||||
include_children=True,
|
||||
)
|
||||
]
|
||||
monitoring_client.alert_policies = [] # no alert -> must NOT credit
|
||||
|
||||
check = (
|
||||
logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled()
|
||||
)
|
||||
result = check.execute()
|
||||
|
||||
child = [r for r in result if r.project_id == GCP_PROJECT_ID]
|
||||
assert child and all(r.status == "FAIL" for r in child), [
|
||||
(r.project_id, r.status) for r in result
|
||||
]
|
||||
|
||||
+170
@@ -259,3 +259,173 @@ class Test_logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_ena
|
||||
assert result[0].resource_name == "metric_name"
|
||||
assert result[0].project_id == GCP_PROJECT_ID
|
||||
assert result[0].location == GCP_EU1_LOCATION
|
||||
|
||||
def test_project_centrally_covered_via_org_aggregated_sink(self):
|
||||
"""A child project with NO local metric, but whose org has an aggregated
|
||||
sink (includeChildren=True) routing its logs to a central bucket that has
|
||||
a bucket-scoped metric + alert, should PASS (covered centrally)."""
|
||||
logging_client = MagicMock()
|
||||
monitoring_client = MagicMock()
|
||||
org_id = "111222333"
|
||||
central_bucket = (
|
||||
"projects/central-logging-project/locations/eu/buckets/central-bucket"
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled.logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled.logging_client",
|
||||
new=logging_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled.logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled.monitoring_client",
|
||||
new=monitoring_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.gcp.models import GCPOrganization, GCPProject
|
||||
from prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled.logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled import (
|
||||
logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled,
|
||||
)
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
Metric,
|
||||
Sink,
|
||||
)
|
||||
from prowler.providers.gcp.services.monitoring.monitoring_service import (
|
||||
AlertPolicy,
|
||||
)
|
||||
|
||||
logging_client.region = GCP_EU1_LOCATION
|
||||
logging_client.project_ids = [GCP_PROJECT_ID, "central-logging-project"]
|
||||
logging_client.projects = {
|
||||
GCP_PROJECT_ID: GCPProject(
|
||||
id=GCP_PROJECT_ID,
|
||||
number="123456789012",
|
||||
name="child",
|
||||
labels={},
|
||||
lifecycle_state="ACTIVE",
|
||||
organization=GCPOrganization(
|
||||
id=org_id, name=f"organizations/{org_id}"
|
||||
),
|
||||
)
|
||||
}
|
||||
logging_client.metrics = [
|
||||
Metric(
|
||||
name="central-metric",
|
||||
type="logging.googleapis.com/user/central-metric",
|
||||
filter='resource.type="gce_firewall_rule" AND (protoPayload.methodName:"compute.firewalls.patch" OR protoPayload.methodName:"compute.firewalls.insert" OR protoPayload.methodName:"compute.firewalls.delete")',
|
||||
project_id="central-logging-project",
|
||||
bucket_name=central_bucket,
|
||||
)
|
||||
]
|
||||
logging_client.sinks = [
|
||||
Sink(
|
||||
name="org-aggregated-sink",
|
||||
destination=f"logging.googleapis.com/{central_bucket}",
|
||||
filter="all",
|
||||
project_id=f"organizations/{org_id}",
|
||||
include_children=True,
|
||||
)
|
||||
]
|
||||
monitoring_client.alert_policies = [
|
||||
AlertPolicy(
|
||||
name="projects/central-logging-project/alertPolicies/ap",
|
||||
display_name="central-alert",
|
||||
enabled=True,
|
||||
filters=[
|
||||
'metric.type = "logging.googleapis.com/user/central-metric"'
|
||||
],
|
||||
project_id="central-logging-project",
|
||||
)
|
||||
]
|
||||
|
||||
check = (
|
||||
logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled()
|
||||
)
|
||||
result = check.execute()
|
||||
|
||||
assert any(
|
||||
r.project_id == GCP_PROJECT_ID
|
||||
and r.status == "PASS"
|
||||
and "aggregated sink" in r.status_extended
|
||||
for r in result
|
||||
), [(r.project_id, r.status, r.status_extended) for r in result]
|
||||
|
||||
def test_aggregated_sink_metric_without_alert_still_fails(self):
|
||||
"""Guard: an org aggregated sink + a bucket-scoped metric matching the filter
|
||||
but with NO alert must NOT credit the child project — it should still FAIL."""
|
||||
logging_client = MagicMock()
|
||||
monitoring_client = MagicMock()
|
||||
org_id = "111222333"
|
||||
central_bucket = (
|
||||
"projects/central-logging-project/locations/eu/buckets/central-bucket"
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled.logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled.logging_client",
|
||||
new=logging_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled.logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled.monitoring_client",
|
||||
new=monitoring_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.gcp.models import GCPOrganization, GCPProject
|
||||
from prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled.logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled import (
|
||||
logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled,
|
||||
)
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
Metric,
|
||||
Sink,
|
||||
)
|
||||
|
||||
logging_client.region = GCP_EU1_LOCATION
|
||||
logging_client.project_ids = [GCP_PROJECT_ID, "central-logging-project"]
|
||||
logging_client.projects = {
|
||||
GCP_PROJECT_ID: GCPProject(
|
||||
id=GCP_PROJECT_ID,
|
||||
number="123456789012",
|
||||
name="child",
|
||||
labels={},
|
||||
lifecycle_state="ACTIVE",
|
||||
organization=GCPOrganization(
|
||||
id=org_id, name=f"organizations/{org_id}"
|
||||
),
|
||||
)
|
||||
}
|
||||
logging_client.metrics = [
|
||||
Metric(
|
||||
name="central-metric",
|
||||
type="logging.googleapis.com/user/central-metric",
|
||||
filter='resource.type="gce_firewall_rule" AND (protoPayload.methodName:"compute.firewalls.patch" OR protoPayload.methodName:"compute.firewalls.insert" OR protoPayload.methodName:"compute.firewalls.delete")',
|
||||
project_id="central-logging-project",
|
||||
bucket_name=central_bucket,
|
||||
)
|
||||
]
|
||||
logging_client.sinks = [
|
||||
Sink(
|
||||
name="org-aggregated-sink",
|
||||
destination=f"logging.googleapis.com/{central_bucket}",
|
||||
filter="all",
|
||||
project_id=f"organizations/{org_id}",
|
||||
include_children=True,
|
||||
)
|
||||
]
|
||||
monitoring_client.alert_policies = [] # no alert -> must NOT credit
|
||||
|
||||
check = (
|
||||
logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled()
|
||||
)
|
||||
result = check.execute()
|
||||
|
||||
child = [r for r in result if r.project_id == GCP_PROJECT_ID]
|
||||
assert child and all(r.status == "FAIL" for r in child), [
|
||||
(r.project_id, r.status) for r in result
|
||||
]
|
||||
|
||||
+170
@@ -259,3 +259,173 @@ class Test_logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled:
|
||||
assert result[0].resource_name == "metric_name"
|
||||
assert result[0].project_id == GCP_PROJECT_ID
|
||||
assert result[0].location == GCP_EU1_LOCATION
|
||||
|
||||
def test_project_centrally_covered_via_org_aggregated_sink(self):
|
||||
"""A child project with NO local metric, but whose org has an aggregated
|
||||
sink (includeChildren=True) routing its logs to a central bucket that has
|
||||
a bucket-scoped metric + alert, should PASS (covered centrally)."""
|
||||
logging_client = MagicMock()
|
||||
monitoring_client = MagicMock()
|
||||
org_id = "111222333"
|
||||
central_bucket = (
|
||||
"projects/central-logging-project/locations/eu/buckets/central-bucket"
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled.logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled.logging_client",
|
||||
new=logging_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled.logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled.monitoring_client",
|
||||
new=monitoring_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.gcp.models import GCPOrganization, GCPProject
|
||||
from prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled.logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled import (
|
||||
logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled,
|
||||
)
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
Metric,
|
||||
Sink,
|
||||
)
|
||||
from prowler.providers.gcp.services.monitoring.monitoring_service import (
|
||||
AlertPolicy,
|
||||
)
|
||||
|
||||
logging_client.region = GCP_EU1_LOCATION
|
||||
logging_client.project_ids = [GCP_PROJECT_ID, "central-logging-project"]
|
||||
logging_client.projects = {
|
||||
GCP_PROJECT_ID: GCPProject(
|
||||
id=GCP_PROJECT_ID,
|
||||
number="123456789012",
|
||||
name="child",
|
||||
labels={},
|
||||
lifecycle_state="ACTIVE",
|
||||
organization=GCPOrganization(
|
||||
id=org_id, name=f"organizations/{org_id}"
|
||||
),
|
||||
)
|
||||
}
|
||||
logging_client.metrics = [
|
||||
Metric(
|
||||
name="central-metric",
|
||||
type="logging.googleapis.com/user/central-metric",
|
||||
filter='resource.type="gce_network" AND (protoPayload.methodName:"compute.networks.insert" OR protoPayload.methodName:"compute.networks.patch" OR protoPayload.methodName:"compute.networks.delete" OR protoPayload.methodName:"compute.networks.removePeering" OR protoPayload.methodName:"compute.networks.addPeering")',
|
||||
project_id="central-logging-project",
|
||||
bucket_name=central_bucket,
|
||||
)
|
||||
]
|
||||
logging_client.sinks = [
|
||||
Sink(
|
||||
name="org-aggregated-sink",
|
||||
destination=f"logging.googleapis.com/{central_bucket}",
|
||||
filter="all",
|
||||
project_id=f"organizations/{org_id}",
|
||||
include_children=True,
|
||||
)
|
||||
]
|
||||
monitoring_client.alert_policies = [
|
||||
AlertPolicy(
|
||||
name="projects/central-logging-project/alertPolicies/ap",
|
||||
display_name="central-alert",
|
||||
enabled=True,
|
||||
filters=[
|
||||
'metric.type = "logging.googleapis.com/user/central-metric"'
|
||||
],
|
||||
project_id="central-logging-project",
|
||||
)
|
||||
]
|
||||
|
||||
check = (
|
||||
logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled()
|
||||
)
|
||||
result = check.execute()
|
||||
|
||||
assert any(
|
||||
r.project_id == GCP_PROJECT_ID
|
||||
and r.status == "PASS"
|
||||
and "aggregated sink" in r.status_extended
|
||||
for r in result
|
||||
), [(r.project_id, r.status, r.status_extended) for r in result]
|
||||
|
||||
def test_aggregated_sink_metric_without_alert_still_fails(self):
|
||||
"""Guard: an org aggregated sink + a bucket-scoped metric matching the filter
|
||||
but with NO alert must NOT credit the child project — it should still FAIL."""
|
||||
logging_client = MagicMock()
|
||||
monitoring_client = MagicMock()
|
||||
org_id = "111222333"
|
||||
central_bucket = (
|
||||
"projects/central-logging-project/locations/eu/buckets/central-bucket"
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled.logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled.logging_client",
|
||||
new=logging_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled.logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled.monitoring_client",
|
||||
new=monitoring_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.gcp.models import GCPOrganization, GCPProject
|
||||
from prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled.logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled import (
|
||||
logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled,
|
||||
)
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
Metric,
|
||||
Sink,
|
||||
)
|
||||
|
||||
logging_client.region = GCP_EU1_LOCATION
|
||||
logging_client.project_ids = [GCP_PROJECT_ID, "central-logging-project"]
|
||||
logging_client.projects = {
|
||||
GCP_PROJECT_ID: GCPProject(
|
||||
id=GCP_PROJECT_ID,
|
||||
number="123456789012",
|
||||
name="child",
|
||||
labels={},
|
||||
lifecycle_state="ACTIVE",
|
||||
organization=GCPOrganization(
|
||||
id=org_id, name=f"organizations/{org_id}"
|
||||
),
|
||||
)
|
||||
}
|
||||
logging_client.metrics = [
|
||||
Metric(
|
||||
name="central-metric",
|
||||
type="logging.googleapis.com/user/central-metric",
|
||||
filter='resource.type="gce_network" AND (protoPayload.methodName:"compute.networks.insert" OR protoPayload.methodName:"compute.networks.patch" OR protoPayload.methodName:"compute.networks.delete" OR protoPayload.methodName:"compute.networks.removePeering" OR protoPayload.methodName:"compute.networks.addPeering")',
|
||||
project_id="central-logging-project",
|
||||
bucket_name=central_bucket,
|
||||
)
|
||||
]
|
||||
logging_client.sinks = [
|
||||
Sink(
|
||||
name="org-aggregated-sink",
|
||||
destination=f"logging.googleapis.com/{central_bucket}",
|
||||
filter="all",
|
||||
project_id=f"organizations/{org_id}",
|
||||
include_children=True,
|
||||
)
|
||||
]
|
||||
monitoring_client.alert_policies = [] # no alert -> must NOT credit
|
||||
|
||||
check = (
|
||||
logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled()
|
||||
)
|
||||
result = check.execute()
|
||||
|
||||
child = [r for r in result if r.project_id == GCP_PROJECT_ID]
|
||||
assert child and all(r.status == "FAIL" for r in child), [
|
||||
(r.project_id, r.status) for r in result
|
||||
]
|
||||
|
||||
+170
@@ -259,3 +259,173 @@ class Test_logging_log_metric_filter_and_alert_for_vpc_network_route_changes_ena
|
||||
assert result[0].resource_name == "metric_name"
|
||||
assert result[0].project_id == GCP_PROJECT_ID
|
||||
assert result[0].location == GCP_EU1_LOCATION
|
||||
|
||||
def test_project_centrally_covered_via_org_aggregated_sink(self):
|
||||
"""A child project with NO local metric, but whose org has an aggregated
|
||||
sink (includeChildren=True) routing its logs to a central bucket that has
|
||||
a bucket-scoped metric + alert, should PASS (covered centrally)."""
|
||||
logging_client = MagicMock()
|
||||
monitoring_client = MagicMock()
|
||||
org_id = "111222333"
|
||||
central_bucket = (
|
||||
"projects/central-logging-project/locations/eu/buckets/central-bucket"
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled.logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled.logging_client",
|
||||
new=logging_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled.logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled.monitoring_client",
|
||||
new=monitoring_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.gcp.models import GCPOrganization, GCPProject
|
||||
from prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled.logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled import (
|
||||
logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled,
|
||||
)
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
Metric,
|
||||
Sink,
|
||||
)
|
||||
from prowler.providers.gcp.services.monitoring.monitoring_service import (
|
||||
AlertPolicy,
|
||||
)
|
||||
|
||||
logging_client.region = GCP_EU1_LOCATION
|
||||
logging_client.project_ids = [GCP_PROJECT_ID, "central-logging-project"]
|
||||
logging_client.projects = {
|
||||
GCP_PROJECT_ID: GCPProject(
|
||||
id=GCP_PROJECT_ID,
|
||||
number="123456789012",
|
||||
name="child",
|
||||
labels={},
|
||||
lifecycle_state="ACTIVE",
|
||||
organization=GCPOrganization(
|
||||
id=org_id, name=f"organizations/{org_id}"
|
||||
),
|
||||
)
|
||||
}
|
||||
logging_client.metrics = [
|
||||
Metric(
|
||||
name="central-metric",
|
||||
type="logging.googleapis.com/user/central-metric",
|
||||
filter='resource.type="gce_route" AND (protoPayload.methodName:"compute.routes.delete" OR protoPayload.methodName:"compute.routes.insert")',
|
||||
project_id="central-logging-project",
|
||||
bucket_name=central_bucket,
|
||||
)
|
||||
]
|
||||
logging_client.sinks = [
|
||||
Sink(
|
||||
name="org-aggregated-sink",
|
||||
destination=f"logging.googleapis.com/{central_bucket}",
|
||||
filter="all",
|
||||
project_id=f"organizations/{org_id}",
|
||||
include_children=True,
|
||||
)
|
||||
]
|
||||
monitoring_client.alert_policies = [
|
||||
AlertPolicy(
|
||||
name="projects/central-logging-project/alertPolicies/ap",
|
||||
display_name="central-alert",
|
||||
enabled=True,
|
||||
filters=[
|
||||
'metric.type = "logging.googleapis.com/user/central-metric"'
|
||||
],
|
||||
project_id="central-logging-project",
|
||||
)
|
||||
]
|
||||
|
||||
check = (
|
||||
logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled()
|
||||
)
|
||||
result = check.execute()
|
||||
|
||||
assert any(
|
||||
r.project_id == GCP_PROJECT_ID
|
||||
and r.status == "PASS"
|
||||
and "aggregated sink" in r.status_extended
|
||||
for r in result
|
||||
), [(r.project_id, r.status, r.status_extended) for r in result]
|
||||
|
||||
def test_aggregated_sink_metric_without_alert_still_fails(self):
|
||||
"""Guard: an org aggregated sink + a bucket-scoped metric matching the filter
|
||||
but with NO alert must NOT credit the child project — it should still FAIL."""
|
||||
logging_client = MagicMock()
|
||||
monitoring_client = MagicMock()
|
||||
org_id = "111222333"
|
||||
central_bucket = (
|
||||
"projects/central-logging-project/locations/eu/buckets/central-bucket"
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_gcp_provider(),
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled.logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled.logging_client",
|
||||
new=logging_client,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled.logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled.monitoring_client",
|
||||
new=monitoring_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.gcp.models import GCPOrganization, GCPProject
|
||||
from prowler.providers.gcp.services.logging.logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled.logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled import (
|
||||
logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled,
|
||||
)
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
Metric,
|
||||
Sink,
|
||||
)
|
||||
|
||||
logging_client.region = GCP_EU1_LOCATION
|
||||
logging_client.project_ids = [GCP_PROJECT_ID, "central-logging-project"]
|
||||
logging_client.projects = {
|
||||
GCP_PROJECT_ID: GCPProject(
|
||||
id=GCP_PROJECT_ID,
|
||||
number="123456789012",
|
||||
name="child",
|
||||
labels={},
|
||||
lifecycle_state="ACTIVE",
|
||||
organization=GCPOrganization(
|
||||
id=org_id, name=f"organizations/{org_id}"
|
||||
),
|
||||
)
|
||||
}
|
||||
logging_client.metrics = [
|
||||
Metric(
|
||||
name="central-metric",
|
||||
type="logging.googleapis.com/user/central-metric",
|
||||
filter='resource.type="gce_route" AND (protoPayload.methodName:"compute.routes.delete" OR protoPayload.methodName:"compute.routes.insert")',
|
||||
project_id="central-logging-project",
|
||||
bucket_name=central_bucket,
|
||||
)
|
||||
]
|
||||
logging_client.sinks = [
|
||||
Sink(
|
||||
name="org-aggregated-sink",
|
||||
destination=f"logging.googleapis.com/{central_bucket}",
|
||||
filter="all",
|
||||
project_id=f"organizations/{org_id}",
|
||||
include_children=True,
|
||||
)
|
||||
]
|
||||
monitoring_client.alert_policies = [] # no alert -> must NOT credit
|
||||
|
||||
check = (
|
||||
logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled()
|
||||
)
|
||||
result = check.execute()
|
||||
|
||||
child = [r for r in result if r.project_id == GCP_PROJECT_ID]
|
||||
assert child and all(r.status == "FAIL" for r in child), [
|
||||
(r.project_id, r.status) for r in result
|
||||
]
|
||||
|
||||
@@ -137,3 +137,166 @@ class TestLoggingService:
|
||||
s for s in logging_svc.sinks if s.project_id.startswith("organizations/")
|
||||
]
|
||||
assert org_sinks == []
|
||||
|
||||
def test_get_metrics_populates_bucket_name(self):
|
||||
"""_get_metrics() captures a metric's bucketName (for aggregated-sink crediting)."""
|
||||
bucket = "projects/central-logging-project/locations/eu/buckets/central-bucket"
|
||||
mock_client = MagicMock()
|
||||
mock_client.sinks().list().execute.return_value = {"sinks": []}
|
||||
mock_client.sinks().list_next.return_value = None
|
||||
mock_client.projects().metrics().list().execute.return_value = {
|
||||
"metrics": [
|
||||
{
|
||||
"name": "central-metric",
|
||||
"metricDescriptor": {
|
||||
"type": "logging.googleapis.com/user/central-metric"
|
||||
},
|
||||
"filter": "severity>=ERROR",
|
||||
"bucketName": bucket,
|
||||
}
|
||||
]
|
||||
}
|
||||
mock_client.projects().metrics().list_next.return_value = None
|
||||
|
||||
with (
|
||||
patch(
|
||||
"prowler.providers.gcp.lib.service.service.GCPService.__is_api_active__",
|
||||
new=mock_is_api_active,
|
||||
),
|
||||
patch(
|
||||
"prowler.providers.gcp.lib.service.service.GCPService.__generate_client__",
|
||||
return_value=mock_client,
|
||||
),
|
||||
):
|
||||
logging_svc = Logging(set_mocked_gcp_provider(project_ids=[GCP_PROJECT_ID]))
|
||||
|
||||
metrics = [m for m in logging_svc.metrics if m.name == "central-metric"]
|
||||
assert len(metrics) == 1
|
||||
assert metrics[0].bucket_name == bucket
|
||||
|
||||
|
||||
class TestGetProjectsCoveredByAggregatedMetric:
|
||||
"""Unit tests for the aggregated-sink crediting helper: one positive case and the
|
||||
guards that must NOT credit a project (so the metric-filter checks never false-pass).
|
||||
"""
|
||||
|
||||
FILTER = 'protoPayload.methodName="SetIamPolicy"'
|
||||
ORG = "111222333"
|
||||
BUCKET = "projects/central-logging-project/locations/eu/buckets/central-bucket"
|
||||
|
||||
def _clients(
|
||||
self,
|
||||
*,
|
||||
include_children=True,
|
||||
bucket_name=None,
|
||||
sink_destination=None,
|
||||
sink_filter="all",
|
||||
with_alert=True,
|
||||
project_org_id=None,
|
||||
):
|
||||
from prowler.providers.gcp.models import GCPOrganization, GCPProject
|
||||
from prowler.providers.gcp.services.logging.logging_service import Metric, Sink
|
||||
from prowler.providers.gcp.services.monitoring.monitoring_service import (
|
||||
AlertPolicy,
|
||||
)
|
||||
|
||||
bucket_name = self.BUCKET if bucket_name is None else bucket_name
|
||||
sink_destination = (
|
||||
f"logging.googleapis.com/{self.BUCKET}"
|
||||
if sink_destination is None
|
||||
else sink_destination
|
||||
)
|
||||
project_org_id = self.ORG if project_org_id is None else project_org_id
|
||||
|
||||
logging_client = MagicMock()
|
||||
logging_client.project_ids = [GCP_PROJECT_ID]
|
||||
logging_client.projects = {
|
||||
GCP_PROJECT_ID: GCPProject(
|
||||
id=GCP_PROJECT_ID,
|
||||
number="123456789012",
|
||||
name="child",
|
||||
labels={},
|
||||
lifecycle_state="ACTIVE",
|
||||
organization=GCPOrganization(
|
||||
id=project_org_id, name=f"organizations/{project_org_id}"
|
||||
),
|
||||
)
|
||||
}
|
||||
logging_client.metrics = [
|
||||
Metric(
|
||||
name="central-metric",
|
||||
type="logging.googleapis.com/user/central-metric",
|
||||
filter=self.FILTER,
|
||||
project_id="central-logging-project",
|
||||
bucket_name=bucket_name,
|
||||
)
|
||||
]
|
||||
logging_client.sinks = [
|
||||
Sink(
|
||||
name="org-sink",
|
||||
destination=sink_destination,
|
||||
filter=sink_filter,
|
||||
project_id=f"organizations/{self.ORG}",
|
||||
include_children=include_children,
|
||||
)
|
||||
]
|
||||
monitoring_client = MagicMock()
|
||||
monitoring_client.alert_policies = (
|
||||
[
|
||||
AlertPolicy(
|
||||
name="projects/central-logging-project/alertPolicies/ap",
|
||||
display_name="central-alert",
|
||||
enabled=True,
|
||||
filters=[
|
||||
'metric.type = "logging.googleapis.com/user/central-metric"'
|
||||
],
|
||||
project_id="central-logging-project",
|
||||
)
|
||||
]
|
||||
if with_alert
|
||||
else []
|
||||
)
|
||||
return logging_client, monitoring_client
|
||||
|
||||
def _run(self, logging_client, monitoring_client):
|
||||
from prowler.providers.gcp.services.logging.logging_service import (
|
||||
get_projects_covered_by_aggregated_metric,
|
||||
)
|
||||
|
||||
return get_projects_covered_by_aggregated_metric(
|
||||
logging_client, monitoring_client, self.FILTER
|
||||
)
|
||||
|
||||
def test_covered_when_all_conditions_met(self):
|
||||
logging_client, monitoring_client = self._clients()
|
||||
assert self._run(logging_client, monitoring_client) == {
|
||||
GCP_PROJECT_ID: "central-metric"
|
||||
}
|
||||
|
||||
def test_not_covered_without_alert(self):
|
||||
logging_client, monitoring_client = self._clients(with_alert=False)
|
||||
assert self._run(logging_client, monitoring_client) == {}
|
||||
|
||||
def test_not_covered_when_metric_not_bucket_scoped(self):
|
||||
logging_client, monitoring_client = self._clients(bucket_name="")
|
||||
assert self._run(logging_client, monitoring_client) == {}
|
||||
|
||||
def test_not_covered_when_sink_not_include_children(self):
|
||||
logging_client, monitoring_client = self._clients(include_children=False)
|
||||
assert self._run(logging_client, monitoring_client) == {}
|
||||
|
||||
def test_not_covered_when_sink_filter_is_restrictive(self):
|
||||
logging_client, monitoring_client = self._clients(
|
||||
sink_filter='resource.type="gce_instance"'
|
||||
)
|
||||
assert self._run(logging_client, monitoring_client) == {}
|
||||
|
||||
def test_not_covered_when_sink_destination_bucket_differs(self):
|
||||
logging_client, monitoring_client = self._clients(
|
||||
sink_destination="logging.googleapis.com/projects/x/locations/eu/buckets/other"
|
||||
)
|
||||
assert self._run(logging_client, monitoring_client) == {}
|
||||
|
||||
def test_not_covered_when_project_org_differs(self):
|
||||
logging_client, monitoring_client = self._clients(project_org_id="999999999")
|
||||
assert self._run(logging_client, monitoring_client) == {}
|
||||
|
||||
Reference in New Issue
Block a user