DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • When Guest Access Becomes an Attack Surface: A Technical Analysis of the City-Forum Campaign
  • How to Secure Fintech REST APIs Against BOLA Vulnerabilities
  • 5 Infrastructure Controls for Securing AI Agents
  • Why AWS and Azure Handle Data Perimeter Differently

Trending

  • Deploying an Enterprise LLM Chatbot on Databricks With RAG, MLflow, Vector Search, and Model Serving
  • Building Meeting Audio RAG on Microsoft Foundry With Fast Transcription and Foundry IQ
  • From Chat Completions to Responses: Why Is OpenAI Upgrading Its Core API?
  • From Agile to the Product Operating Model
  1. DZone
  2. Software Design and Architecture
  3. Security
  4. Why Your Terraform Drift Alerts Are Useless (And How to Fix Them)

Why Your Terraform Drift Alerts Are Useless (And How to Fix Them)

Terraform plan treats all drift equally, creating alert fatigue. A four-tier severity model (critical, high, medium, and low) based on resource type and attributes.

By 
Sudarshan Bhagvan Thakur user avatar
Sudarshan Bhagvan Thakur
·
Aug. 27, 26 · Presentation
Likes (0)
Comment
Save
Tweet
Share
71 Views

Join the DZone community and get the full member experience.

Join For Free

Let me describe a workflow that exists in thousands of engineering organizations right now.

Somebody sets up a cron job. It runs terraform plan against production every few hours. When the plan output isn't empty, it fires a Slack notification. The team calls this "drift detection."

For about two weeks, it works. Engineers look at every alert, investigate changes, and fix things. Then the noise starts. Auto-scaling groups change desired_capacity. It's not drift; that's the system doing its job. Someone updated a tag through the cost allocation tool. An external script modified a description field. The load balancer's idle timeout was changed by an automation nobody remembers writing.

Within a month, the Slack channel is muted. Within two months, the cron job is either disabled or silently ignored. And that's when someone modifies a security group through the AWS console "temporarily" and forgets to revert it.

I've seen this pattern at every organization I've worked at. The problem isn't that drift detection doesn't work. It works well. It finds everything, tells you nothing about what matters and what is actually important, and eventually drowns in its own noise.

The Signal-to-Noise Problem

Here's the main issue with terraform plan as a drift detection mechanism. Something changed, or it didn't. There's no concept of severity, no notion of risk, no way to distinguish between a tag modification and an exposed database. We cannot tell from the change how much of a risk that is.

Consider two drift events:

  • Event A: aws_s3_bucket.logs the tags.Environment attribute changed from "production" to "prod"
  • Event B:aws_security_group.api_gateway — the inbound rule  now includes a rule allowing port 22 from 0.0.0.0/0

Terraform plan presents both as equivalent changes. But Event A is a cosmetic inconsistency that has zero operational impact. Event B is an active security vulnerability that could be the first step in a breach.

When you're getting 40 alerts a day and most of them look like Event A, how long does it take before you stop carefully examining each one?

Studies on alert fatigue show that when engineers are flooded with too many alerts, it becomes harder to respond effectively. As a result, critical issues can be overlooked along with less important alerts. Monitoring tools addressed this problem years ago by prioritizing alerts based on severity and sending them to the right teams. Infrastructure drift detection has not yet adopted these practices.

Thinking in Severity Tiers

The solution isn't to stop detecting drift. It's to classify it.

When I started building a drift detection tool for my own use, severity classification was the feature I cared about most. After iterating on several models, I landed on four tiers:

  • Critical: Changes that directly affect security boundaries. If someone modified a security group's ingress rules, an IAM policy, a KMS key policy, or an S3 public access configuration, I want to know about it right now. 
  • High: Changes that affect compute capacity, data persistence, or encryption. An instance type change in production means your capacity planning is wrong. A database with publicly_accessible flipped to true is a problem waiting to happen. An encryption setting change needs investigation.
  • Medium: the default bucket for attribute changes that don't match any explicit rule. Worth knowing about, not worth getting paged for.
  • Low: Tags, descriptions, labels. The metadata that external systems modify constantly and that nobody needs to be alerted about.

At first, I tried using three severity levels. However, that was too simple because it did not clearly separate different types of serious issues. For example, changing an IAM policy could create a security risk, while changing an instance type could cause performance or capacity problems. Both are important, but they have different impacts.

I also tried using five severity levels, but that was too detailed. It became difficult to consistently decide which level an issue belonged to, especially when the differences between levels were small.

Attribute-Level Classification

The key insight is that severity depends on which attribute changed, not just which resource type changed.

An aws_security_group resource changing its tags is low severity. The same resource changing its ingress rules is critical. Classifying by resource type alone would make all security group changes critical, which defeats the purpose. You'd still get noise from tag modifications.

The classification engine I built uses pattern matching rules that match against the resource type and attribute combination. For example: aws_security_group..ingress maps to critical, aws_security_group..tags maps to low, aws_iam_policy..policy maps to critical, aws_instance..instance_type maps to high, and any *.tags pattern maps to low.

When a resource has multiple changed attributes at different severity levels, the maximum applies. A security group with both a tag change (low) and an ingress change (critical) gets reported as critical. This prevents the scenario where someone dismisses a critical alert because it's attached to what looks like a mostly-harmless tag update.

I chose fnmatch glob patterns over regular expressions deliberately. The people editing these rules are operations engineers responding to incidents at 2 AM, not writing parsers. A pattern like aws_security_group.*.ingress is instantly readable.

The Numbers

I tested this approach across 150+ Terraform workspaces managing 847 AWS resources. I introduced 62 drift events across four categories: security-relevant changes (security group and IAM modifications), operational changes (instance types, database configs), metadata changes (tags, descriptions), and expected changes (auto-scaling adjustments).

With binary detection (standard terraform plan), all 62 drift events were flagged as 100% of changes, with security-relevant ones buried in noise. Filtering to High and Critical severity only reduced the alert count to 17 (27% of total) while still catching 7 of 8 security-relevant changes  94% security coverage. Adding ignore rules for expected drift like autoscaling reduced it further to just 12 alerts (19% of total) at the same 94% security coverage.

That's a 73% reduction in alert volume while retaining 94% of security-relevant changes.

The severity classification also performed well against manual expert review. Two engineers independently labeled all 62 events. Agreement rates with automated classification: critical 96%, high 91%, medium 88%, low 95%.

The Ignore Layer

Beyond severity classification, there's a category of drift that shouldn't be classified at all it should be filtered out entirely.

Auto-scaling groups change desired_capacity every few minutes. That's not drift. That's the autoscaler doing exactly what it's supposed to do. ECS services change desired_count for the same reason. Tag attributes like LastModified get updated by external tools constantly.

An ignore file (similar in concept to .gitignore) handles this. You list patterns like aws_autoscaling_group..desired_capacity and aws_ecs_service..desired_count, and those changes are filtered out before classification, removing an entire class of noise without any risk to security coverage.

Configuration as Institutional Knowledge

Here's something I didn't anticipate when I started building this: the severity configuration file becomes a living document of your organization's security values.

When you mark a rule like aws_rds_instance.*.storage_encrypted as critical, you are defining what is important for your environment. When you add a new pattern after an incident, you are documenting a lesson learned. Over time, this knowledge is stored in a version-controlled YAML file instead of relying on team members to remember it. So when a new engineer asks, "Do we care about CloudFront origin changes?", they can find the answer directly in the configuration.

That incident comment in the config file is institutional knowledge being captured and enforced, not just documented.

Cross-Cloud Applicability

The pattern-based approach works across cloud providers. For Azure, patterns like azurerm_network_security_group..security_rule, azurerm_role_assignment. and azurerm_key_vault_access_policy.* map to critical, while azurerm_virtual_machine.*.vm_size maps to high.

For GCP, patterns like google_compute_firewall..allow, google_compute_firewall..source_ranges, and google_project_iam_binding.* map to critical, while google_compute_instance.*.machine_type maps to high.

The severity tiers are universal. The patterns are provider-specific. A well-maintained rule set should cover the top 20-30 most security-sensitive resource types and attributes for each cloud provider you use.

From Detection to Governance

Severity classification opens the door to something more powerful than alerting: governance.

Once drift has a severity score, you can build policies around it. In CI/CD, you can fail the deployment pipeline if Critical drift exists in the target environment. For escalation routing, you can send critical drift to PagerDuty, high to Slack, and log medium/low silently for weekly review. For auto-remediation, you can automatically run terraform apply for low-severity drift like tag corrections but require human approval for anything high or above. For compliance, you can generate weekly reports showing drift by severity for security review.

Getting Started

If you want to try this approach, tfdrift is the open-source tool I built implementing everything described in this article. Install it with pip install tfdrift, then run tfdrift scan --path ./your-terraform-dir to scan your infrastructure. Run tfdrift init to generate a starter configuration file.

It ships with 60+ built-in severity rules for AWS, Azure, and GCP, all configurable via YAML. It supports Slack and PagerDuty notifications, JSON/Markdown/HTML output, auto-remediation with safety guards, and OpenTofu via a --binary flag.

But the specific tool matters less than the approach. The core idea — classifying drift by security impact and routing alerts accordingly — is implementable with any combination of terraform plan, a JSON parser, and a pattern matcher.

Key Takeaways

Binary drift detection creates alert fatigue. When all changes are treated equally, teams stop checking, and that's when security-critical changes get missed.

Four severity tiers hit the right granularity. Critical for security boundaries, high for compute and encryption, medium for other changes, and low for metadata. Three is too coarse, five is too hard to distinguish consistently.

Classify by attribute, not just resource type. A security group changing tags is low, but the same resource changing ingress rules is critical. Attribute-level classification is what makes severity useful.

Severity filtering reduces alert volume by 73% while maintaining 94% security coverage based on evaluation across 150+ Terraform workspaces.

The severity config becomes institutional knowledge. Your configuration file is a version-controlled, reviewable record of what your organization considers security-critical infrastructure changes.

security

Opinions expressed by DZone contributors are their own.

Related

  • When Guest Access Becomes an Attack Surface: A Technical Analysis of the City-Forum Campaign
  • How to Secure Fintech REST APIs Against BOLA Vulnerabilities
  • 5 Infrastructure Controls for Securing AI Agents
  • Why AWS and Azure Handle Data Perimeter Differently

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook