Back to Checks
aws

CloudWatch Logs metric filter and alarm exist for changes to network gateways

cloudwatch_changes_to_network_gateways_alarm_configured
Service: cloudwatchSeverity: medium
OfficialBuilt-inVerified
Categories:logging
Resource Type:AwsCloudWatchAlarm

CloudWatch log metric filters and alarms for network gateway changes are identified by matching CloudTrail events such as CreateCustomerGateway, DeleteCustomerGateway, AttachInternetGateway, CreateInternetGateway, DeleteInternetGateway, and DetachInternetGateway in log groups that receive trail logs.

Risk

Without this monitoring, gateway changes can expose private networks to the Internet or break connectivity. Adversaries or mistakes can enable data exfiltration, bypass network inspection, and trigger outages via deletions or detachments, impacting confidentiality and availability.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler aws --checks cloudwatch_changes_to_network_gateways_alarm_configured

Recommendation

Send CloudTrail to CloudWatch Logs and create a metric filter for the listed gateway events with an alarm that notifies responders. Enforce least privilege for gateway modifications, require change approvals, and route alerts to monitored channels as part of defense in depth.

Remediation

Terraform

# CloudWatch Logs metric filter for network gateway changes
resource "aws_cloudwatch_log_metric_filter" "<example_resource_name>" {
  name           = "<example_resource_name>"
  log_group_name = "<example_resource_name>"
  pattern        = "{ ($.eventName = CreateCustomerGateway) || ($.eventName = DeleteCustomerGateway) || ($.eventName = AttachInternetGateway) || ($.eventName = CreateInternetGateway) || ($.eventName = DeleteInternetGateway) || ($.eventName = DetachInternetGateway) }" # Critical: matches gateway change events

  metric_transformation {
    name      = "<example_resource_name>"
    namespace = "<example_resource_name>"
    value     = "1"
  }
}

# Alarm on the metric filter
resource "aws_cloudwatch_metric_alarm" "<example_resource_name>" {
  alarm_name          = "<example_resource_name>"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 1
  metric_name         = "<example_resource_name>"   # Critical: must match metric from the filter
  namespace           = "<example_resource_name>"
  period              = 300
  statistic           = "Sum"
  threshold           = 1
}

NativeIaC

# CloudFormation: Create metric filter and alarm for network gateway changes
Resources:
  NetworkGatewayMetricFilter:
    Type: AWS::Logs::MetricFilter
    Properties:
      LogGroupName: <example_resource_name>
      FilterPattern: '{ ($.eventName = CreateCustomerGateway) || ($.eventName = DeleteCustomerGateway) || ($.eventName = AttachInternetGateway) || ($.eventName = CreateInternetGateway) || ($.eventName = DeleteInternetGateway) || ($.eventName = DetachInternetGateway) }'  # Critical: matches gateway change events
      MetricTransformations:
        - MetricName: <example_resource_name>
          MetricNamespace: <example_resource_name>
          MetricValue: "1"

  NetworkGatewayAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      ComparisonOperator: GreaterThanOrEqualToThreshold
      EvaluationPeriods: 1
      MetricName: <example_resource_name>  # Critical: alarm targets the metric created by the filter
      Namespace: <example_resource_name>
      Period: 300
      Statistic: Sum
      Threshold: 1

Other

1In the AWS Console, go to CloudWatch > Logs > Log groups and open the CloudTrail log group
2Create metric filter:
3- Filter pattern: { ($.eventName = CreateCustomerGateway) || ($.eventName = DeleteCustomerGateway) || ($.eventName = AttachInternetGateway) || ($.eventName = CreateInternetGateway) || ($.eventName = DeleteInternetGateway) || ($.eventName = DetachInternetGateway) }
4- Metric name: <example_resource_name>
5- Metric namespace: <example_resource_name>
6- Metric value: 1
7From the filter, choose Create alarm:
8- Statistic: Sum, Period: 5 minutes, Threshold: >= 1, Evaluation periods: 1
9- Create the alarm (actions optional)

Check Code

from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.cloudtrail.cloudtrail_client import (
    cloudtrail_client,
)
from prowler.providers.aws.services.cloudwatch.cloudwatch_client import (
    cloudwatch_client,
)
from prowler.providers.aws.services.cloudwatch.lib.metric_filters import (
    build_metric_filter_pattern,
    check_cloudwatch_log_metric_filter,
)
from prowler.providers.aws.services.cloudwatch.logs_client import logs_client


class cloudwatch_changes_to_network_gateways_alarm_configured(Check):
    """CloudWatch Logs metric filter and alarm exist for changes to network gateways.

    Looks for a CloudWatch Logs metric filter matching the expected pattern on a
    log group used by a CloudTrail trail, with at least one alarm on its metric.

    - PASS: A matching metric filter with an associated alarm exists.
    - FAIL: No matching metric filter, or a filter without an alarm, was found.
    - MANUAL: CloudTrail trails, log groups, metric filters or alarms could
      not be listed in at least one region, so the absence of a filter/alarm
      cannot be asserted.
    """

    def execute(self) -> list[Check_Report_AWS]:
        """Evaluate the metric filter and alarm coverage for the account.

        Returns:
            list[Check_Report_AWS]: A single report for the account.
        """
        pattern = build_metric_filter_pattern(
            event_names=[
                "CreateCustomerGateway",
                "DeleteCustomerGateway",
                "AttachInternetGateway",
                "CreateInternetGateway",
                "DeleteInternetGateway",
                "DetachInternetGateway",
            ],
        )
        findings = []

        report = check_cloudwatch_log_metric_filter(
            pattern,
            cloudtrail_client.trails,
            logs_client.metric_filters,
            cloudwatch_client.metric_alarms,
            self.metadata(),
        )

        inventory_unavailable = (
            cloudtrail_client.trails_unavailable
            or logs_client.log_groups_unavailable
            or logs_client.metric_filters_unavailable
            or cloudwatch_client.metric_alarms_unavailable
        )

        if report is None:
            report = Check_Report_AWS(metadata=self.metadata(), resource={})
            report.status = "FAIL"
            report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated."
            report.region = logs_client.region
            report.resource_id = logs_client.audited_account
            report.resource_arn = logs_client.log_group_arn_template
            report.resource_tags = []

        # A denied listing in any region means the inventory is incomplete: a
        # PASS is still backed by a real filter and alarm, but a FAIL (nothing
        # found, or a filter found without its alarm) cannot be trusted.
        if report.status == "FAIL" and inventory_unavailable:
            report.status = "MANUAL"
            report.status_extended = "Cannot evaluate CloudWatch metric filters and alarms: CloudTrail trails, log groups, metric filters or alarms could not be listed in at least one region. Verify that the scanning credentials are allowed to call cloudtrail:DescribeTrails, logs:DescribeLogGroups, logs:DescribeMetricFilters and cloudwatch:DescribeAlarms."

        findings.append(report)

        return findings