This check flags GCP projects that have no log-based metric and alert watching for Cloud SQL configuration changes. Without it, someone can disable backups, open the instance to the world, or drop deletion protection and nobody gets paged. Create a log-based metric filtering Cloud SQL admin activity and wire an alerting policy to it.
Cloud SQL runs the databases behind a lot of production systems, which makes its configuration a high-value target. A single API call can flip an instance from private to publicly reachable, turn off automated backups, or strip deletion protection. The problem is not that these calls are hard to make. The problem is that, by default, nobody is watching when they happen.
The No Alert for Cloud SQL Configuration Changes check looks at whether your GCP project has a log-based alert that fires when someone modifies a Cloud SQL instance. If there is no metric and no alerting policy tied to those events, the check fails.
What this check detects
GCP writes every administrative action against Cloud SQL to Cloud Audit Logs under the cloudsql.googleapis.com service. That includes creating, updating, patching, and deleting instances, as well as changing flags, backup settings, and network authorization.
This check verifies two things exist and are connected:
- A log-based metric whose filter matches Cloud SQL configuration change events.
- A Cloud Monitoring alerting policy that triggers on that metric and notifies a channel (email, PagerDuty, Slack, and so on).
If either piece is missing, the changes still get logged, but no human or system is told about them in real time.
Note: Audit logging and alerting are separate concerns. Admin Activity audit logs are enabled by default and cannot be turned off, so the events are almost certainly being recorded. This check is specifically about whether you act on them.
Why it matters
Configuration changes to a database are rarely neutral. The ones that matter most for security tend to happen quietly and look like routine administration in the logs. Consider a few concrete scenarios.
An instance gets exposed to the internet
Adding 0.0.0.0/0 to a Cloud SQL instance's authorized networks makes it reachable from anywhere. Combined with a weak or reused database password, that is a direct path to data exfiltration. If you only review network exposure during quarterly audits, an attacker has months to work with.
Backups get disabled before a destructive action
A common ransomware and sabotage pattern is to remove recovery options first. Turning off automated backups, then deleting point-in-time recovery, leaves you with nothing to restore from when the instance is later wiped. Each of those is a separate config change, and each one is a chance to catch the attacker before the irreversible step.
Deletion protection gets stripped
Deletion protection is the guardrail standing between a fat-fingered command and a gone-forever production database. When someone disables it, that is worth knowing about immediately, whether it was a mistake or a precursor to deletion.
Warning: Several of these changes can be made by service accounts, not just humans. A compromised CI/CD credential with the cloudsql.admin role can do all of this without a single person logging into the console. Alerting on the action, regardless of who performed it, is what catches this.
The business impact is straightforward: data breach, data loss, downtime, and compliance findings. Frameworks like CIS GCP and PCI DSS expect you to monitor and alert on changes to systems holding regulated data, so an empty alerting policy here is also an audit liability.
How to fix it
The fix has two steps. First create a log-based metric that counts Cloud SQL configuration changes, then attach an alerting policy with a notification channel.
Step 1: Create the log-based metric
Pick a filter that captures Cloud SQL admin activity. This one matches updates and patches against Cloud SQL instances:
gcloud logging metrics create cloudsql-config-changes \
--description="Cloud SQL instance configuration changes" \
--project=YOUR_PROJECT_ID \
--log-filter='protoPayload.serviceName="cloudsql.googleapis.com"
AND (protoPayload.methodName="cloudsql.instances.update"
OR protoPayload.methodName="cloudsql.instances.patch"
OR protoPayload.methodName="cloudsql.instances.delete"
OR protoPayload.methodName="cloudsql.instances.create")'
If you want a tighter signal, drop the create method and keep the ones that change or remove existing instances. Creation of a new instance is usually expected churn, while updates and deletes are where the security-relevant changes live.
Step 2: Create a notification channel (if you do not have one)
gcloud beta monitoring channels create \
--display-name="DB Security Alerts" \
--type=email \
[email protected] \
--project=YOUR_PROJECT_ID
Note the channel ID it returns. You will reference it in the alerting policy.
Step 3: Create the alerting policy
The cleanest way to define the policy is with a JSON file, since it lets you version-control the whole thing. Save this as cloudsql-alert-policy.json:
{
"displayName": "Cloud SQL Configuration Changes",
"combiner": "OR",
"conditions": [
{
"displayName": "Cloud SQL config change detected",
"conditionThreshold": {
"filter": "metric.type=\"logging.googleapis.com/user/cloudsql-config-changes\" AND resource.type=\"cloudsql_database\"",
"comparison": "COMPARISON_GT",
"thresholdValue": 0,
"duration": "0s",
"aggregations": [
{
"alignmentPeriod": "60s",
"perSeriesAligner": "ALIGN_COUNT"
}
]
}
}
],
"notificationChannels": [
"projects/YOUR_PROJECT_ID/notificationChannels/CHANNEL_ID"
],
"alertStrategy": {
"autoClose": "1800s"
}
}
Apply it:
gcloud alpha monitoring policies create \
--policy-from-file=cloudsql-alert-policy.json \
--project=YOUR_PROJECT_ID
Tip: If you manage more than a couple of projects, do not click through the console for each one. Define the metric and policy once as a module and roll it out everywhere. The Terraform approach below scales much better than per-project CLI runs.
Console steps (if you prefer the UI)
- Go to Logging > Logs-based Metrics and click Create Metric.
- Set the type to Counter, name it
cloudsql-config-changes, and paste the log filter from Step 1. - Go to Monitoring > Alerting and click Create Policy.
- Select your new log-based metric, set the condition to is above 0, and use a rolling window of 1 minute.
- Attach a notification channel and save.
Doing it as code with Terraform
Manual fixes drift. A project created next month will not have the metric unless someone remembers to add it. Define the whole thing in Terraform and the gap closes on its own.
resource "google_logging_metric" "cloudsql_config_changes" {
name = "cloudsql-config-changes"
project = var.project_id
filter = <<-EOT
protoPayload.serviceName="cloudsql.googleapis.com"
AND (protoPayload.methodName="cloudsql.instances.update"
OR protoPayload.methodName="cloudsql.instances.patch"
OR protoPayload.methodName="cloudsql.instances.delete")
EOT
metric_descriptor {
metric_kind = "DELTA"
value_type = "INT64"
}
}
resource "google_monitoring_alert_policy" "cloudsql_config_changes" {
display_name = "Cloud SQL Configuration Changes"
project = var.project_id
combiner = "OR"
conditions {
display_name = "Cloud SQL config change detected"
condition_threshold {
filter = "metric.type=\"logging.googleapis.com/user/${google_logging_metric.cloudsql_config_changes.name}\" AND resource.type=\"cloudsql_database\""
comparison = "COMPARISON_GT"
threshold_value = 0
duration = "0s"
aggregations {
alignment_period = "60s"
per_series_aligner = "ALIGN_COUNT"
}
}
}
notification_channels = [var.db_alert_channel_id]
alert_strategy {
auto_close = "1800s"
}
}
Drop this into a shared module and apply it across every project that runs Cloud SQL. New projects inherit the alert by default rather than as an afterthought.
How to prevent it from happening again
Fixing one project is the easy part. Keeping the control in place across a growing estate is the real work.
- Bake it into your landing zone. If you provision projects through a factory pipeline or Terraform module, include the metric and alerting policy in that baseline so every new project ships with it.
- Gate it in CI/CD. Use a policy-as-code tool like OPA or Conftest against your Terraform plan to fail the build if a project hosts Cloud SQL but defines no corresponding alerting policy.
- Scan continuously. Run Lensix on a schedule so a deleted or never-created alert shows up as a finding instead of staying invisible until an incident.
- Use organization policies for the riskiest settings. Pair alerting with the
constraints/sql.restrictPublicIporg policy constraint so public IP cannot even be enabled. Alerting catches what prevention misses, and prevention reduces what alerting has to catch.
Danger: Do not test this by actually opening a production instance to 0.0.0.0/0 to see if the alert fires. Validate the pipeline in a sandbox project, or trigger a harmless change like updating a database flag on a throwaway instance. A real exposure, even briefly, can be scanned and exploited within minutes.
Best practices
An alert that nobody reads is only marginally better than no alert at all. A few things keep this control useful over time.
- Route to a channel people actually watch. Database config changes outside a deploy window deserve a page, not a buried email. Send them somewhere your on-call rotation sees them.
- Add context to the notification. Include the principal email (
protoPayload.authenticationInfo.principalEmail) and the instance name in your alert documentation so responders know who did what without digging through logs. - Distinguish expected from unexpected. If your deploy pipeline patches instances regularly, those events will fire the alert. Annotate or suppress changes made by your known automation service account, and treat anything else as a genuine signal.
- Cover the whole sensitive surface, not just Cloud SQL. The same log-based metric pattern applies to IAM changes, firewall rules, and bucket permissions. Build the muscle once and apply it across your audit-relevant resources.
- Review your filter periodically. GCP adds and renames API methods. Confirm your
methodNamelist still matches the changes you care about, especially for newer Cloud SQL features.
The goal is simple: when someone changes how your production database is configured, you find out in seconds, not during the post-incident review. This check is the difference between those two outcomes.

