Chapter 8 — Reps
Conditioning, not grading. This week the keyboard is the gym for one skill above all: producing infrastructure as code and then verifying it — including verifying the code an AI wrote. You’ll write idempotent declarative tasks by hand, hunt seeded defects in AI-generated IaC, and wire the validate-and-gate funnel that makes trust cheap.
Ground rules:
- Type every line yourself. No copy-paste, no agent. Your fingers learn the idempotent task pattern (
state: present,creates:,changed_when:) only by typing it until it’s automatic. - AI stays OFF — Phase 1. You may read about coding agents (§8.4); you may not use one to do a rep. You cannot gate an agent’s IaC later if you have never written idempotent IaC, or caught a hallucinated module, with your own hands. Build the reflex first.
- Predict, then verify. Before you run any linter or
--check, write down what you expect it to flag. The gap between your prediction and the tool’s output is the lesson — and it’s exactly the muscle the midterm grades. - Re-run everything. Idempotency is only proven on the second run. Any “converge” rep is incomplete until a second pass reports a clean
okwith nochanged. - Read before you apply. Every rep that ends in a plan or a dry-run: read the diff line by line and say out loud what would change. No exceptions, not even in practice.
These reps assume a Linux box or VM (Appendix A) with ansible/ansible-lint, terraform (or tofu), and tflint installed. The chapter’s code/ folder has the artifacts you’ll verify. Destructive steps are marked [DESTRUCTIVE]; run them only in a throwaway VM.
Reps 1–3: Declarative, Idempotent, and the First Hunt
Rep 1 — Imperative → Idempotent
Here is an imperative bash setup. Convert it to an idempotent Ansible playbook by hand.
apt-get install -y nginx
systemctl enable --now nginx
echo "hello chapel" > /var/www/html/index.html
Write play1.yml using ansible.builtin.package, ansible.builtin.service, and ansible.builtin.copy (not shell/command). Run it with --check --diff, then for real, then a second time. Predict which tasks report changed on run 1 vs run 2. A correct conversion reports all ok (zero changed) on the second run. Write one sentence: which line of the bash version was not idempotent, and why your Ansible version is.
Rep 2 — Prove non-idempotency hurts
Deliberately write the wrong version: a task using ansible.builtin.shell to echo the index file instead of copy. Run it twice. Note that it reports changed both times. Now add creates: /var/www/html/index.html (or changed_when: false where appropriate) and re-run. Write down what changed in the output and explain, in §8.2’s vocabulary, what “drift detection” you just lost and regained.
Rep 3 — The hunt: find the four bugs by eye
Open code/webserver.yml — an AI-generated playbook. Before running any tool, read it line by line and write down, on paper, every defect you find and which of the four classes it is: hallucinated module, insecure default, plaintext secret, wrong-order/non-idempotent. There are at least four. Then run ansible-lint code/webserver.yml and compare. Which did the linter catch? Which did it miss? Write the one defect a linter could never catch and why (hint: it doesn’t know your org’s policy).
Reps 4–6: Terraform and the Validation Funnel
Rep 4 — Provision, then read the plan
Write a minimal main.tf that declares one local_file resource (no cloud account needed):
resource "local_file" "greeting" {
filename = "${path.module}/greeting.txt"
content = "Grace and peace.\n"
}
Run terraform init, terraform plan, then terraform apply. Read the plan before applying and write the one-line summary it gives (1 to add). Run plan again after apply and confirm it now says “No changes.” Write one sentence on how Terraform knew nothing needed to change (hint: the state file).
Rep 5 — Break it, watch the plan
Edit the content of your local_file and run terraform plan again. Note that it now shows ~ update in-place (or -/+ destroy and then create). Write down which it chose and why. This is the dry-run that tells you what will change — the artifact §8.6 says you must never skip.
Rep 6 — The funnel on real defects
Run terraform init -backend=false then terraform validate on code/storage.tf. It should reject the file. Predict which line before you read the error. Then add tflint and (if installed) tfsec/checkov. List every defect each tool reports: the hallucinated auto_tiering argument, the public-read ACL, the missing encryption/versioning, the hard-coded region. Fix them all into a storage_fixed.tf that validates clean and would pass a “no public buckets, encryption required” policy. Write one sentence per fix.
Reps 7–9: Prompting, CI, and Secret Scanning
Rep 7 — A weak prompt vs a strong one (paper rep)
You will not run an AI (Phase 1). Instead, on paper, write the weak prompt “write Ansible for a web server” and list the four defects you’d expect an AI to produce for it (you saw them in Rep 3). Then rewrite it as a strong prompt using §8.5’s three rules: constrain explicitly (TLS-only, no inline secrets, least privilege), demand idempotency by name, and state that you will verify. Write one sentence on why the prompt is not where trust is established.
Rep 8 — A CI gate that blocks a secret
Write a tiny CI job (a Makefile target or a .github/workflows/iac.yml step) that runs a secret scan and fails the build on a finding. If you don’t have gitleaks, use a grep:
# fail (exit 1) if any plaintext password assignment appears
! grep -rnE 'password:\s*["'\'']?[A-Za-z0-9@]' . --include='*.yml'
Plant a deliberate password: "P@ssw0rd123" in a test file and confirm the job exits non-zero. Remove it, confirm the job passes. Write one sentence: why must this gate be blocking, not advisory?
Rep 9 — Run the chapter’s verifier end to end
Make code/verify_iac.sh executable and run it on the two defective artifacts:
chmod +x code/verify_iac.sh
./code/verify_iac.sh code/webserver.yml .
Confirm it exits 1 (BLOCKED). Now fix the artifacts (from Reps 3 and 6) and re-run until it prints PASS. Write down which of the four verifier stages caught which defect — and the one thing the script prints even on PASS (“a human still reads every line”). Why does it say that?
Reps 10–11: The Human Gate and Excessive Agency
Rep 10 — Wire the approval gate
Use code/approval_gate.py as the step between plan and apply. Pipe it a benign plan and a destructive one:
echo "terraform plan: + local_file.greeting" | python3 code/approval_gate.py # auto-approved
echo "terraform plan: - aws_db_instance (destroy)" | python3 code/approval_gate.py # HOLD
Confirm the destructive plan halts for a human and that nothing proceeds unless a human types the exact acknowledgment. Open audit.log and confirm both decisions were recorded. Write one sentence mapping this gate to OWASP LLM06 (Excessive Agency).
Rep 11 — The Rule of Two, on paper
Take three IaC-agent capabilities: (a) reads your private repo, (b) ingests a third-party dependency README / GitHub issue, (c) can open an outbound PR or call a cloud API. For each pair and the full set, decide using Meta’s Rule of Two whether the agent may run unsupervised. Write down which single capability you’d remove to make an “all three” task safe again, and name the attack (indirect prompt injection / the lethal trifecta) you just defused.
Done? One Last Thing.
This is the midterm in miniature — do it against a 25-minute clock, AI off, book open.
You are handed a fresh AI-generated playbook and a Terraform snippet (use code/webserver.yml and code/storage.tf, or have a classmate seed new defects). In 25 minutes:
- Verify: find and name every defect, by the four classes, by eye first — then confirm with the funnel (
ansible-lint,terraform validate,tflint, the secret grep). - Correct: produce
webserver_fixed.ymlandstorage_fixed.tfthat lint clean, are idempotent (clean--check/plansecond pass), and would pass a least-privilege + encryption + no-public + TLS-only policy. - Trust: write four sentences — one per defect class — defending why your corrected version is now safe to apply, and one more sentence naming the single change you would and would not delegate to an AI agent in Phase 2, and why.
If you can do all three under the clock with the book open and no agent, you are ready for Project 8. If you can’t, that gap is exactly the conditioning to close before Monday. Run it again.
Up next: Project 8 — Project 8: Generate, Verify, Trust (the midterm — 60 minutes, closed-AI, closed-internet, open-textbook), with a cumulative review of Weeks 1–7.