---
name: audit-infrastructure-as-code
description: Audit OpenTofu/Terraform and Ansible infrastructure-as-code files for security issues — remote state, firewall rules, sudo grants, secrets management, file permissions, and supply-chain hygiene. Starter skill; more checks will be added over time.
argument-hint: "[path/to/infra/]"
---

# Audit Infrastructure as Code

Scan OpenTofu/Terraform (`.tf`) files and Ansible role files for security issues.

> **Note**: This is a starter skill. It covers the most impactful checks drawn
> from real Gemma client deployments. More comprehensive checks will be added in
> future versions.

**Argument**: `$ARGUMENTS`

---

## Step 1 — Locate IaC files

If `$ARGUMENTS` is provided, use it as the base directory. Otherwise, use the
current working directory.

Use the Glob tool:

- `**/*.tf` — OpenTofu / Terraform files
- `**/ansible/roles/*/tasks/main.yml` and `**/roles/*/tasks/main.yml` — Ansible task files
- `**/ansible/roles/*/templates/*.j2` and `**/roles/*/templates/*.j2` — Ansible templates
- `**/ansible/roles/*/vars/main.yml`, `**/group_vars/**/*.yml`, `**/host_vars/**/*.yml` — Ansible variable files
- `**/*.tfstate` and `**/*.tfstate.backup` — committed state files (always check, regardless of other findings)

If neither `.tf` files nor Ansible directories are found, output:

> No IaC files detected (no `*.tf`, `ansible/`, or `roles/` found under
> `<base directory>`). Skipping infrastructure-as-code audit.

Then stop.

Report which tool types were detected:
- OpenTofu found: yes/no (N `.tf` files)
- Ansible found: yes/no (N roles, M task files)

---

## Step 2 — Read IaC files

Read all located files using the Read tool.

**OpenTofu**: cap at 20 `.tf` files. If more exist, prioritise files named
`main.tf`, `firewall.tf`, `security.tf`, `variables.tf`, and `outputs.tf`.
Note any files skipped due to the cap.

**Ansible**: read all `tasks/main.yml`, `templates/*.j2`, and
`vars/main.yml` / `group_vars/` / `host_vars/` files found.

---

## Step 3 — Read the checklist and apply checks

Read `${CLAUDE_SKILL_DIR}/references/checklist.md`. Apply each check in the
OpenTofu section and the Ansible section to the file contents read in Step 2.

The checklist contains full pass/fail criteria, fail examples, and remediation
for all 13 checks. Apply them as written. The procedural notes below cover
detection nuances not in the checklist.

### OpenTofu procedural notes

**Local state detection (Remote state check)**

Detect a `terraform {}` block in any `.tf` file. Within that block, look for
a `backend "<type>"` sub-block:

- No `terraform {}` block present → FAIL (local state, no remote backend configured)
- `terraform {}` block exists but no `backend` sub-block → FAIL (local state)
- `backend "s3"`, `backend "remote"`, `backend "gcs"`, or similar present → evaluate locking as per checklist → PASS if locking is configured

**Firewall SSH check — provider-specific attribute names**

Locate port 22 rules in firewall resources. The attribute to inspect differs by provider:

- Hetzner (`hcloud_firewall`): check `source_ips`
- AWS (`aws_security_group` / `aws_security_group_rule`): check `cidr_blocks` or `security_group_id` in ingress rules
- GCP (`google_compute_firewall`): check `source_ranges`
- Unknown provider: check for any IP restriction attribute; flag if `0.0.0.0/0` appears in a port 22 rule

FAIL if `0.0.0.0/0` or `::/0` is present in any matching SSH ingress rule.

**Committed state files (Critical check)**

Glob for `*.tfstate` and `*.tfstate.backup` anywhere in the repo. If any are
found, record as a Critical finding regardless of other results. State files
contain all resource attributes including generated passwords and connection
strings.

### Ansible procedural notes

**Encrypted vars (Secrets check)**

Before flagging a variable value as a hardcoded secret, inspect the value:

- If the value starts with `$ANSIBLE_VAULT;` → treat as encrypted → PASS for that variable
- If the value is a Jinja2 `{{ }}` expression → not hardcoded → PASS for that variable
- All other non-empty string literals matching secret-like patterns → FAIL as per checklist criteria

---

## Step 4 — Output findings

### Header

```
## Infrastructure-as-Code Security Audit
Base directory: <path>
OpenTofu: [found — N files] or [not found]
Ansible: [found — N roles, M task files] or [not found]
Note: Starter skill — covers highest-impact checks. More checks will be added.
```

### Findings by tool and severity

Output OpenTofu findings first, then Ansible findings. Within each section,
group by severity:

```
### OpenTofu findings

#### Critical
#### Important
#### Recommended

### Ansible findings

#### Critical
#### Important
#### Recommended
```

Each finding entry:

```
**[CHECK NAME]** — `<file>:<line>`
<One-sentence description of what was found and the risk.>
Fix: <key remediation — the essential change required.>
```

If no findings exist for a section:

> No findings — all checked OpenTofu / Ansible patterns passed.

### Summary table

| Check | Tool | Status | Findings |
|---|---|---|---|
| Remote state with locking | OpenTofu | PASS / FAIL / SKIP | |
| Committed state files | OpenTofu | PASS / FAIL / SKIP | |
| SSH firewall restricted | OpenTofu | PASS / FAIL / SKIP | |
| No NOPASSWD:ALL in cloud-init | OpenTofu | PASS / FAIL / SKIP | |
| Correct user groups in cloud-init | OpenTofu | PASS / FAIL / SKIP | |
| OS image pinned | OpenTofu | PASS / FAIL / SKIP | |
| Private network for inter-service | OpenTofu | PASS / FAIL / SKIP | |
| Backups enabled | OpenTofu | PASS / FAIL / SKIP | |
| Provider versions pinned | OpenTofu | PASS / FAIL / SKIP | |
| Secrets from vault, not hardcoded | Ansible | PASS / FAIL / SKIP | |
| no_log on secret-handling tasks | Ansible | PASS / FAIL / SKIP | |
| .env files mode 0600 | Ansible | PASS / FAIL / SKIP | |
| Dedicated deploy key for Git | Ansible | PASS / FAIL / SKIP | |
| Docker build uses --target | Ansible | PASS / FAIL / SKIP | |

### Passed checks

Brief bullet list of all checks with no findings.

---

## Notes

- This skill is read-only. It does not modify any IaC files.
- Never include actual secret values in output — refer to findings by variable
  name and location only.
