Capstone — Automating ws1's Web Server Hardening
Ansible
Chapter 10 · Capstone — Automating ws1's Web Server Hardening
The final chapter. ws1's own web-server hardening course was built entirely by hand — typing commands over SSH, editing files directly. This capstone turns five of its eight chapters into one real, idempotent, version-controlled Ansible project — the exact "fix the config, not just the console" discipline ansible1-1 opened this whole course with, finally made concrete.
The Target — ws1's Own Hardening Course, Automated
Five of ws1's eight chapters, each becoming its own role, per ansible1-7's own DRY principle: HTTPS via Let's Encrypt, firewall via UFW, fail2ban, SSH hardening, and Apache security headers.
Role 1 — firewall (UFW)
Role 2 — ssh_hardening
lineinfile — ansible1-4's own example of a genuinely idempotent edit, checking the file's current content before deciding whether to change anything.
Role 3 — https (Let's Encrypt)
certbot is invoked via command, since no dedicated module covers this exact flow — but the creates argument is ansible1-4's own fix for command/shell's non-idempotency trap, applied for real: the task only actually runs if the certificate doesn't already exist.
Role 4 — fail2ban
ansible1-5's own template module, doing exactly what it was introduced to do — rendering a real config file per host, with ansible1-6's own handler restarting the service only when that file genuinely changed.
Role 5 — security_headers (Apache)
ansible1-5's own {% for %} loop example, no longer a teaching snippet — this is the actual mechanism generating ws1's own Apache security-header configuration, driven by a plain, editable list of headers rather than typed by hand into a config file.
Secrets — Vault-Protecting admin_email and Anything Sensitive
ansible1-8's own encrypt_string, applied to admin_email (and any API token a real deployment might also need) — the rest of the vars file stays fully readable, only the sensitive value itself is hidden.
Verifying Idempotency — Running the Whole Playbook Twice
ansible1-6's own practice, applied to the whole capstone: changed=0 on the second run is the actual proof this playbook is safe to re-run on a schedule, as a real, ongoing defense against configuration drift — not a claim taken on faith.
| Piece | Chapter |
|---|---|
| Role structure, one per ws1 concern | ansible1-7 |
| lineinfile idempotent edit | ansible1-4 |
| command + creates for certbot | ansible1-4 |
| template + Jinja2 for-loop | ansible1-5 |
| notify/handlers, restart-on-change | ansible1-6 |
| Vault-encrypted admin_email | ansible1-8 |
| Second-run idempotency verification | ansible1-6 |
| Provisioning/configuring division of labor | ansible1-1, tf1-1 |
ws1's eight chapters — ClamAV and Monitoring & Maintenance are left out, to keep the capstone focused rather than exhaustive. No Molecule tests were written for these specific roles (ansible1-9 covered the technique; applying it here is a genuine next step, not done in this chapter). This capstone uses a plain, static inventory — a deliberate choice for a small, fixed set of known servers, not ansible1-9's own dynamic inventory, which solves a different problem. And every role here is Debian-specific, matching ws1's own scope exactly — none of it is portable to a RedHat-family host without real additional when: conditions.
Hands-On Exercises
Write the fail2ban role's own task (using the template module, matching the chapter's own pattern) that deploys jail.local and notifies a handler named "restart fail2ban" only when the file's content actually changes.
📄 View solutionExplain why the certbot task uses command with a creates argument instead of running unconditionally on every playbook run, referencing ansible1-4's own idempotency material directly.
📄 View solutionA colleague asks why admin_email needs Vault protection when it's "just an email address, not really a secret." Give an honest answer, and name one genuinely sensitive value this capstone's own roles would need to protect the same way in a real deployment.
📄 View solutionChapter 10 Quick Reference — Ansible Complete
- Five ws1 chapters (HTTPS, UFW, fail2ban, SSH hardening, Apache security headers) become five roles, one playbook
- lineinfile and command+creates deliver ansible1-4's own idempotent-edit and idempotent-escape-hatch patterns for real
- template + Jinja2 {% for %} generates real Apache config from a plain list of headers, not hand-typed text
- notify/handlers restart services only when something genuinely changed — ansible1-6's own principle, applied
- Vault protects admin_email and any real secrets, staying fully readable elsewhere in the vars file
- changed=0 on a second run is the actual, checkable proof this playbook is safe to re-run on a schedule
- Honest scope note: ClamAV/Monitoring left out, no Molecule tests written here, static (not dynamic) inventory, Debian-only
- The full Ansible course — 10 chapters — is now complete.