This check flags GCP projects that have no log-based metric and alert watching for VPC route changes. Route modifications can silently reroute or blackhole traffic, so create a logs-based metric on compute.routes activity and wire it to a notification channel.
Routes decide where packets go inside your VPC. A single new route can pull traffic toward an attacker-controlled instance, send it through an unexpected NAT, or drop it entirely. The trouble is that route changes rarely look alarming in an audit log unless someone is actively reading it, and nobody reads audit logs by hand. This Lensix check confirms that you have an automated alert in place so a route change wakes someone up instead of sitting unnoticed.
Note: VPC routes in GCP control next-hop forwarding for subnets. They can point at an instance, a VPN tunnel, an internal load balancer, the default internet gateway, or a peering connection. Changing one affects every VM that matches the destination range.
What this check detects
The logging_novpcroutealert check looks at your project's logging configuration and asks a simple question: is there a logs-based metric plus an alerting policy that fires when someone creates, modifies, or deletes a VPC route?
Specifically, it expects a metric filter that matches admin activity on the Compute Engine routes API, something along the lines of:
resource.type="gce_route"
AND (protoPayload.methodName:"compute.routes.delete"
OR protoPayload.methodName:"compute.routes.insert")
If no metric matches that activity, or if a metric exists but has no alerting policy attached to it, the check fails. An alert with no notification channel is treated the same as no alert at all, because nothing reaches a human.
Why it matters
Routing changes are one of the quietest ways to compromise a network. They do not trip firewall alerts, they do not show up as a new public IP, and they do not generate failed-login noise. A route just quietly redirects traffic.
Here are the scenarios that make this worth alerting on:
- Traffic interception. An attacker with
compute.routes.createpermission adds a route for a sensitive destination range with a next hop pointing at an instance they control. Traffic to your database tier now flows through their box first. This is a network-layer man-in-the-middle that leaves almost no trace. - Data exfiltration paths. A new route to
0.0.0.0/0with an internet gateway next hop can open an egress path that bypasses your controlled NAT and egress filtering, defeating the network monitoring you assumed was covering everything. - Denial of service. Deleting or overriding a route can blackhole traffic to an entire subnet. Services go dark, and the cause looks like a mystery outage rather than a config change unless you are watching route activity.
- Lateral movement. Routes added between peered VPCs or toward an internal load balancer can quietly bridge network segments that were meant to be isolated.
This control also maps directly to CIS Google Cloud Platform Foundations Benchmark recommendation 2.8, which is frequently in scope for SOC 2, ISO 27001, and PCI DSS audits. A missing route alert is a recurring audit finding.
Warning: Routes are evaluated by longest-prefix match, then by priority. An attacker does not need to delete your existing routes. A more specific route added on top of yours wins quietly, which means the original route still appears intact in the console.
How to fix it
The fix has two parts: create a logs-based metric that counts route change events, then attach an alerting policy with a notification channel. You need to do this in every project (or centralize it, more on that below).
1. Create the logs-based metric
Using gcloud:
gcloud logging metrics create vpc_route_changes \
--project=YOUR_PROJECT_ID \
--description="Count of VPC route create and delete events" \
--log-filter='resource.type="gce_route"
AND (protoPayload.methodName:"compute.routes.delete"
OR protoPayload.methodName:"compute.routes.insert")'
To do it in the console instead:
- Go to Logging > Logs-based Metrics.
- Click Create Metric and choose Counter.
- Set the metric name to
vpc_route_changes. - Paste the filter above into the Build filter box.
- Click Create metric.
2. Create a notification channel
If you do not already have one, create the channel the alert will use. Email is the simplest, but most teams route to PagerDuty or Slack via a webhook.
gcloud beta monitoring channels create \
--project=YOUR_PROJECT_ID \
--display-name="Network Security On-Call" \
--type=email \
[email protected]
Note the channel ID returned, you will need it in the next step.
3. Create the alerting policy
Define the policy in a JSON file and apply it. This fires whenever the metric records any route change.
{
"displayName": "Alert on VPC Route Changes",
"combiner": "OR",
"conditions": [
{
"displayName": "VPC route change detected",
"conditionThreshold": {
"filter": "metric.type=\"logging.googleapis.com/user/vpc_route_changes\" resource.type=\"gce_route\"",
"comparison": "COMPARISON_GT",
"thresholdValue": 0,
"duration": "0s",
"aggregations": [
{
"alignmentPeriod": "60s",
"perSeriesAligner": "ALIGN_COUNT"
}
]
}
}
],
"notificationChannels": [
"projects/YOUR_PROJECT_ID/notificationChannels/CHANNEL_ID"
]
}
gcloud alpha monitoring policies create \
--project=YOUR_PROJECT_ID \
--policy-from-file=vpc-route-alert-policy.json
Tip: Set thresholdValue to 0 and duration to 0s so the alert fires on the very first event. Route changes are rare and high-impact, so you want immediate notification, not a rolling average that smooths the signal away.
Doing it as Terraform
Clicking through the console does not scale across dozens of projects. Define the metric and alert as code so every project gets identical coverage.
resource "google_logging_metric" "vpc_route_changes" {
name = "vpc_route_changes"
project = var.project_id
filter = <<-EOT
resource.type="gce_route"
AND (protoPayload.methodName:"compute.routes.delete"
OR protoPayload.methodName:"compute.routes.insert")
EOT
metric_descriptor {
metric_kind = "DELTA"
value_type = "INT64"
}
}
resource "google_monitoring_notification_channel" "netsec" {
project = var.project_id
display_name = "Network Security On-Call"
type = "email"
labels = {
email_address = "[email protected]"
}
}
resource "google_monitoring_alert_policy" "vpc_route_changes" {
project = var.project_id
display_name = "Alert on VPC Route Changes"
combiner = "OR"
conditions {
display_name = "VPC route change detected"
condition_threshold {
filter = "metric.type=\"logging.googleapis.com/user/${google_logging_metric.vpc_route_changes.name}\" resource.type=\"gce_route\""
comparison = "COMPARISON_GT"
threshold_value = 0
duration = "0s"
aggregations {
alignment_period = "60s"
per_series_aligner = "ALIGN_COUNT"
}
}
}
notification_channels = [google_monitoring_notification_channel.netsec.id]
}
Note: Logs-based metrics only count entries written after the metric is created. They are not retroactive. If you want history, query the audit logs directly with a gcloud logging read command for past compute.routes events.
How to prevent it from happening again
One-off fixes drift. A project created next quarter will ship without this alert unless you bake the control into how projects are born and how infrastructure is reviewed.
- Bundle it into your project factory. If you provision projects with Terraform modules or Config Controller, add the metric, channel, and alert policy to the baseline module so every new project inherits them automatically.
- Centralize logging. Route audit logs from all projects to a central log sink in a dedicated security project, then build the metric and alert once against the aggregated sink. One alert covers the whole org instead of dozens of per-project copies.
- Gate it in CI/CD. Add a policy check to your pipeline that fails the plan if a project lacks the route-change metric. Tools like Open Policy Agent or Terraform's native checks can assert the resource exists before apply.
- Scan continuously. Let Lensix run
logging_novpcroutealerton a schedule so a deleted or detuned alert surfaces as a finding within hours rather than at audit time.
Tip: The same logs-based-metric pattern covers the rest of the CIS logging recommendations: firewall rule changes, network changes, IAM policy changes, and Cloud Storage IAM changes. Build them all from one Terraform module and you close a whole cluster of findings in one pass.
Best practices
- Treat the network like code. Manage all routes through Terraform or Deployment Manager. When routes only ever change through pull requests, any out-of-band route appearing in an alert is by definition suspicious.
- Restrict who can change routes. The
compute.routes.createandcompute.routes.deletepermissions should belong to a small network admin role, not to broad editor or owner roles. Fewer hands means quieter alerts and clearer accountability. - Send alerts somewhere people act. An email to a shared inbox nobody reads is theater. Route to an on-call rotation in PagerDuty or Opsgenie, or at minimum a monitored Slack channel.
- Test the alert. Create a throwaway route in a sandbox project and confirm the notification actually arrives. An untested alert is an assumption, not a control.
- Include context in the runbook. When the alert fires, responders need to know how to find who made the change. Point them at the audit log entry, which carries
protoPayload.authenticationInfo.principalEmail.
Danger: Do not respond to a route-change alert by immediately deleting the unexpected route in production. If the route is legitimate, removing it can blackhole live traffic. Verify the author and intent first, then revert through your normal change process.
Route changes are cheap for an attacker and expensive for you to miss. The alert costs a few minutes to set up and a few cents a month to run. Wire it once, template it across every project, and let it sit quietly until the day it earns its keep.

