Monday's apply tried to delete the rule fixed by hand on Friday night
Goal
Read changes made outside the code separately with plan -refresh-only, find in the plan JSON what would disappear if applied as is, then carry out the three resolutions of absorbing, reverting, and excluding yourself, and build a detection script to run every day.
Why it matters
A declarative tool takes the code as the truth, so even a correct fix made in the console is reverted on the next apply. So when you find drift, you first read what will disappear, and a person must decide whether to absorb or revert it. ignore_changes is not a switch that hides all drift, and a change to an attribute the provider does not read does not appear in the plan at all. Only by knowing this limit can you tell "it matches" from "it isn't visible" when the detection result is empty.
Steps
- In
/root/iac-drift/main.tf, put arulesvariable (default["allow 443"]); a resource that writes one line per rule toout/firewall.rules, namelylocal_file.fw; and a resource that writes toout/motd.txtthe single linemaintenance window: sun 02:00, namelylocal_file.motd; then run init and apply. - During incident response, someone appended by hand to the end of
/root/iac-drift/out/firewall.rulesthe lineallow 8443(add it yourself). Without touching the code, runtofu plan -refresh-only, which shows only the changes that arose from outside, and save the output to/root/iac-drift/drift.txt. - Without changing the code, save a plan with
tofu plan -out=/root/iac-drift/monday.tfplan(do not apply). Compare what that plan will write tofirewall.ruleswith the current file, and write to/root/iac-drift/lost.txtonly the lines that would disappear if applied. - You judged the emergency fix to be correct. Change the default of
rulesto["allow 443", "allow 8443"]and apply. After that,firewall.rulesmust have two lines and the plan must be clean. - This time someone changed
out/motd.txttomaintenance window: none(change it yourself). This is a wrong change. Before reverting, save the output oftofu plan -refresh-onlyto/root/iac-drift/revert-drift.txt, and with the code left as it is, apply to revert the notice to the code's content. - To
main.tf, add a resource that writes toout/cache.confthe contentcache v1and carrieslifecycle { ignore_changes = [content] }, namelylocal_file.cache, and apply. (a) After changing the file by hand tocache tampered, save the output oftofu planto/root/iac-drift/ignore.txtand revert with apply. (b) Change the code's content tocache v2and see whether the plan is clean (do not apply). (c) Afterchmod 600 out/firewall.rules, write the exit code oftofu plan -detailed-exitcodeto/root/iac-drift/perm.txtasperm=<코드>(the placeholder is the code). - Create
/root/iac-drift/detect.sh <작업디렉터리>(the placeholder is the working directory). It savesplan -refresh-onlyas a plan file and collects the addresses of resources changed from outside, and finishes withOKand 0 if there is no drift, a single lineDRIFT <주소들(공백 구분, 정렬)>and 2 if there is (the placeholder is the addresses, space-separated and sorted), andERRORand 1 if it fails. Leave no files in the working directory. The grader checks by changing files in a copy of/root/iac-drift.
Notes
- The Pod has OpenTofu 1.9.0 and a local provider mirror, so it runs without internet.
- If the content of a local_file differs from the record, it reports the file as "deleted" and plans to recreate it (measured in this Pod). The provider of a cloud resource usually reports an attribute-level difference (an in-place update), so the look on screen differs, but the judgment among absorbing, reverting, and excluding is the same.
- To see only changes that arose from outside:
tofu plan -refresh-only; to accept them into the state only:tofu apply -refresh-only. - Common mistake: applying the moment you see drift and erasing a correct emergency fix. Common mistake: believing that because you put ignore_changes on it, it is left out of detection.
- tofu plan (-refresh-only) · tofu refresh · lifecycle (ignore_changes) · JSON Output Format (resource_drift)
Baseline: apply the firewall rules and the notice from code
In /root/iac-drift/main.tf, put a rules variable (default ["allow 443"]); a resource that writes one line per rule to out/firewall.rules, namely local_file.fw; and a resource that writes to out/motd.txt the single line maintenance window: sun 02:00, namely local_file.motd; then run init and apply.
Use a for expression to attach a newline to each rule and join them together with join. The plan must be empty after applying for this to be the baseline.
See Friday night's emergency fix in the plan
During incident response, someone appended by hand to the end of /root/iac-drift/out/firewall.rules the line allow 8443 (add it yourself). Without touching the code, run tofu plan -refresh-only, which shows only the changes that arose from outside, and save the output to /root/iac-drift/drift.txt.
-refresh-only does not plan changes to the code and shows only the difference between the actual and the state. The local_file provider reports a file as "deleted" if its content differs from the record — confirm in the output that it does not look like one attribute changed.
What disappears if you just apply on Monday
Without changing the code, save a plan with tofu plan -out=/root/iac-drift/monday.tfplan (do not apply). Compare what that plan will write to firewall.rules with the current file, and write to /root/iac-drift/lost.txt only the lines that would disappear if applied.
In resource_changes of the plan JSON, the change.after.content of local_file.fw is the file content after applying. The lines that exist only in the current file are the lines that will disappear. You can get a line-by-line difference with comm or grep -vxF -f.
It was a correct fix — absorb it into the code
You judged the emergency fix to be correct. Change the default of rules to ["allow 443", "allow 8443"] and apply. After that, firewall.rules must have two lines and the plan must be clean.
Absorbing is making the code match reality. Because this provider saw the file as "deleted," even the plan after fixing the code comes out as a creation, not an update — the content to be written is the same as the current file, so the result is the same.
It was a wrong fix — revert it according to the code
This time someone changed out/motd.txt to maintenance window: none (change it yourself). This is a wrong change. Before reverting, save the output of tofu plan -refresh-only to /root/iac-drift/revert-drift.txt, and with the code left as it is, apply to revert the notice to the code's content.
Reverting is making reality match the code. Checking why such a change was made before reverting is part of the procedure — here, you leave the trace of that by saving the drift output.
What ignore_changes blocks, what it cannot block, and what is not seen at all
To main.tf, add a resource that writes to out/cache.conf the content cache v1 and carries lifecycle { ignore_changes = [content] }, namely local_file.cache, and apply. (a) After changing the file by hand to cache tampered, save the output of tofu plan to /root/iac-drift/ignore.txt and revert with apply. (b) Change the code's content to cache v2 and see whether the plan is clean (do not apply). (c) After chmod 600 out/firewall.rules, write the exit code of tofu plan -detailed-exitcode to /root/iac-drift/perm.txt as perm=<코드> (the placeholder is the code).
ignore_changes means "do not plan an update even if the value written in the configuration changes." It cannot prevent the provider from reading the real object and judging that it is gone. And a change to an attribute the provider does not read is not caught as drift.
Drift detection that runs every morning
Create /root/iac-drift/detect.sh <작업디렉터리> (the placeholder is the working directory). It saves plan -refresh-only as a plan file and collects the addresses of resources changed from outside, and finishes with OK and 0 if there is no drift, a single line DRIFT <주소들(공백 구분, 정렬)> and 2 if there is (the placeholder is the addresses, space-separated and sorted), and ERROR and 1 if it fails. Leave no files in the working directory. The grader checks by changing files in a copy of /root/iac-drift.
The plan JSON has resource_drift as well as resource_changes — the changes that arose from outside are held there separately. -detailed-exitcode can be used together with -refresh-only.