# Detect and Fix Infrastructure Drift

Source: https://docs.ops0.com/docs/how-to/detect-drift

Find unauthorized infrastructure changes — whether they happened in the AWS console, CLI, or another tool — and restore your environment to its intended state.

---

## Scenario

Your team manages production AWS infrastructure with Terraform in ops0. A developer made a manual change in the AWS console last week — they opened SSH port 22 to the internet on a security group "just to debug something" and forgot to revert it. You need to find this change and fix it.

**Two types of drift:**

| Type | What it catches | How it works |
|------|-----------------|--------------|
| **Discovery drift** | Any change, including resources not in Terraform | Compare two cloud scan sessions |
| **IaC drift** | Changes to Terraform-managed resources only | Compare live state against Terraform expected state |

---

## Prerequisites

- At least one completed discovery session for the environment

- Cloud integration connected with read permissions — [Connect your cloud](https://docs.ops0.com/docs/getting-started/connect-cloud)

- For IaC drift: an ops0 IaC project managing the resources you want to check

---

## Part 1: Detect Drift with Discovery Session Comparison

Discovery drift compares two scan snapshots and shows exactly what changed — field by field. It catches changes to any resource, even ones not managed by Terraform.

### Run a new scan

1. Navigate to **Discovery** in the left sidebar

2. Click **New Scan** → select your AWS integration

3. Select the same regions and resource types as your previous scan

4. Click **Start Scan** — scans take 3–10 minutes depending on scope

### Compare the sessions

5. Go to **Discovery → Compare**

6. Set **Comparison** to the new session and **Baseline** to the previous session

7. Click **Compare**

### Reading the results

The comparison groups changes into three categories:

| Category | Meaning |
|----------|---------|
| **Modified** | Resources that exist in both sessions but have changed fields — your drift candidates |
| **Added** | Resources present in the new session but not the baseline — created outside IaC |
| **Removed** | Resources present in the baseline but not the new session — deleted outside IaC |

Click any **Modified** resource to see a field-level diff. In our scenario, you'd see:

```
Modified: aws_security_group "web-sg" (sg-0abc123)
  ingress_rules:
    old: [{ port: 443, cidr: 10.0.0.0/8 }]
    new: [{ port: 443, cidr: 10.0.0.0/8 },
           { port: 22,  cidr: 0.0.0.0/0 }]  ← unauthorized change
```

**Focus on security groups and IAM**

When reviewing modified resources, prioritize security groups, IAM roles, and network ACLs — these are the most commonly abused resources in manual changes.

---

## Part 2: Detect Drift with IaC Plan

If the security group is managed by an ops0 Terraform project, run a plan to confirm the drift against your declared state.

1. Open the IaC project that manages the security group

2. Click **Deploy → Run Plan** (no apply yet)

3. Review the plan output — Terraform compares your state file against live cloud

If the security group was changed manually, the plan shows what Terraform would do to reconcile the difference:

```
~ aws_security_group.web
    ingress: [{ port: 443 }] → [{ port: 443 }, { port: 22, cidr: 0.0.0.0/0 }]
```

This confirms the drift. Now you can choose how to fix it.

---

## Part 3: Remediate the Drift

### Option A — Revert the manual change (recommended)

Your existing Terraform doesn't include the SSH rule. Run Apply to restore the correct state.

1. In the IaC project, click **Deploy → Apply**

2. Terraform removes the unauthorized SSH rule and restores the correct ingress configuration

3. Run another **Plan** after apply — it should show **0 changes**

### Option B — Accept the change (update IaC to match reality)

If the change was intentional and you want to keep it, update your Terraform code instead.

1. Edit your Terraform to include the change (e.g. add the SSH rule to the security group resource)

2. Run **Plan** — confirm it shows 0 changes

3. The state is now reconciled — no apply needed

**Which option should I choose?**

If you're unsure whether a change was intentional, treat it as unauthorized (Option A). It's safer to revert and re-apply intentional changes through IaC than to accept a potentially malicious modification.

---

## Part 4: Prevent Future Drift

### Schedule daily scans

Add an automatic scan so drift is caught quickly rather than discovered manually.

1. Go to **Discovery → Schedules → New Schedule**

2. Set frequency to **Daily at 2 AM UTC**

3. Enable **Auto-compare** — ops0 automatically diffs the new scan against the previous one

4. Click **Save**

### Add a policy to block the specific violation

Use an OPA policy to prevent this type of change from reaching production through ops0:

```rego
deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_security_group_rule"
  resource.change.after.from_port == 22
  resource.change.after.cidr_blocks[_] == "0.0.0.0/0"
  msg := "SSH access from 0.0.0.0/0 is not allowed"
}
```

Add this policy under **Policies → New Policy** and attach it to your production IaC project. Plans that introduce this rule will be blocked automatically.

---

## Verification

After remediation, confirm everything is resolved:

- Session comparison shows 0 modified resources (or only intentional changes)

- IaC plan shows **0 changes** after remediation

- Daily scheduled scan has auto-compare enabled

---

## Troubleshooting

Comparison shows too many false positives

Some fields update on every scan (e.g. `last_modified` timestamps, auto-rotating tags). Filter by resource type to focus on meaningful changes, and use the field filter to exclude known noisy attributes.

Plan shows changes but apply doesn't fix them

The resource may have `lifecycle { ignore_changes = [...] }` set in your Terraform code. Check the resource block — ignored attributes are excluded from apply even when they drift.

Can't run comparison — sessions from different integrations

You can compare any two sessions regardless of integration. The diff will show all resources from both. Use the resource type filter to narrow focus to the specific service you're investigating.

---

## Next Steps

[Schedule daily scans → Automate recurring discovery scans to catch drift continuously](https://docs.ops0.com/docs/how-to/schedule-deployments)
[Enforce security policies → Block dangerous infrastructure patterns at plan time with OPA](https://docs.ops0.com/docs/how-to/enforce-security-policies)
[Create approval workflows → Require human sign-off before infrastructure changes are applied](https://docs.ops0.com/docs/how-to/approval-workflows)
[Discovery overview → Learn how discovery scans work and how to configure them](https://docs.ops0.com/docs/how-to/first-discovery)
