GCP Monitoring Alert Policy checks always return `false` due to unfilled placeholder
ScoutSuite version
5.14.0 (latest)
Cloud provider
GCP
Description
MonitoringAlertPolicies._specific_alert_policy_present() in ScoutSuite/providers/gcp/resources/stackdrivermonitoring/monitoring_alert_policies.py always returns False for every check, causing the following findings to be permanently flagged regardless of what alert policies exist in the project:
- Alerts Doesn't Exist for Audit Configuration Changes
- Alerts Doesn't Exist for Cloud Storage IAM Permission Changes
- Alerts Doesn't Exist for Custom Role Changes
- Alerts Doesn't Exist for Project Ownership Assignment/Changes
- Alerts Doesn't Exist for SQL Instance Configuration Changes
- Alerts Doesn't Exist for VPC Network Changes
- Alerts Doesn't Exist for VPC Network Firewall Rule Changes
- Alerts Doesn't Exist for VPC Network Route Changes
Root cause
There are two bugs in _parse_alert_policy and _specific_alert_policy_present:
1. Unfilled placeholder
The filter comparison hardcodes a literal placeholder string that will never match a real alert policy:
if condition.condition_threshold.filter == 'metric.type=\"logging.googleapis.com/user/<Log Metric Name>\"'<Log Metric Name> is never substituted with an actual metric name, so this condition can never be True.
2. All 8 checks call the same function with no arguments
Every dict key in _parse_alert_policy calls self._specific_alert_policy_present(raw_alert_policies) with no way to distinguish which metric is being checked, so even with the placeholder fixed, all 8 checks would return the same result.
Fix
Pass the specific metric name into _specific_alert_policy_present per check, and use a substring match instead of equality (to handle filters that include additional clauses such as AND resource.type=...):
def _parse_alert_policy(self, raw_alert_policies):
alert_policy_dict = {}
alert_policy_dict['project_ownership_assignments'] = \
self._specific_alert_policy_present(raw_alert_policies, 'project_ownership_changes-counter')
alert_policy_dict['audit_config_change'] = \
self._specific_alert_policy_present(raw_alert_policies, 'audit_config_change-counter')
alert_policy_dict['custom_role_change'] = \
self._specific_alert_policy_present(raw_alert_policies, 'custom_role_changes-counter')
alert_policy_dict['vpc_network_firewall_rule_change'] = \
self._specific_alert_policy_present(raw_alert_policies, 'vpc_firewall_network_changes-counter')
alert_policy_dict['vpc_network_route_change'] = \
self._specific_alert_policy_present(raw_alert_policies, 'vpc_network_route_changes-counter')
alert_policy_dict['vpc_network_change'] = \
self._specific_alert_policy_present(raw_alert_policies, 'vpc_network_configuration_changes-counter')
alert_policy_dict['cloud_storage_iam_permission_change'] = \
self._specific_alert_policy_present(raw_alert_policies, 'cloud_storage_iam_changes-counter')
alert_policy_dict['sql_instance_conf_change'] = \
self._specific_alert_policy_present(raw_alert_policies, 'sql_instance_configuration_changes-counter')
return alert_policy_dict
def _specific_alert_policy_present(self, alert_policies, metric_name):
expected_fragment = 'logging.googleapis.com/user/{}'.format(metric_name)
for alert_policy in alert_policies:
if not alert_policy.enabled.value:
continue
for condition in alert_policy.conditions:
if expected_fragment in condition.condition_threshold.filter:
return True
return FalseNotes
The metric names used in the fix above follow the CIS-recommended naming convention. The fix uses a substring match (in) rather than equality (==) to remain compatible with alert policy filters that include additional restrictions such as AND resource.type="global", which the GCP Monitoring API requires in certain cases.
Source: nccgroup/ScoutSuite