From 29de30e867f190303a1496e131d23e9f894c1c43 Mon Sep 17 00:00:00 2001 From: Marjan Gjuroski Date: Sat, 18 Apr 2026 22:02:10 +0200 Subject: [PATCH] Add trivy-scan skill and wire it into infra verification gates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a dedicated trivy-based security scanner as a first-class skill in cloud-infra and makes it a mandatory gate in the existing verification workflow. New skill: /trivy-scan - Runs `trivy config` against the Terraform tree and Helm charts - Honours a `.trivyignore` in the Terraform root, with a convention that every suppression must carry a justifying comment - Preflights the trivy binary and prints an install hint (brew / curl) if missing — never auto-installs - Severity gating: fails on CRITICAL/HIGH/MEDIUM by default; --strict includes LOW; --warn-only never exits non-zero - Reusable `scripts/scan-trivy.sh` consumed by other skills via `bash {plugin-skills-path}/trivy-scan/scripts/scan-trivy.sh` Wired into existing skills - /infra-lint: invokes /trivy-scan as the final step, adds a new quality gate row in the output - /complete-infra: adds Track D (security scan) to Phase 1's parallel lint, adds a quality gate row Agent updates - infra-reviewer: new "Scanner Correlation (trivy)" section. The agent no longer re-runs the scanner; instead it sanity-checks the .trivyignore justifications against the current diff (catches obsolete suppressions, missing reasons, rules targeting deprecated resources). Docs / versioning - cloud-infra version 0.1.0 -> 0.2.0 - cloud-infra README skills table updated - Top-level README: skill count badge + headline 29 -> 30 - Added `trivy` and `security-scanning` keywords to plugin.json Motivation Discovered during issue #15 (SparkReach F01 Azure infra): `terraform validate` and `helm lint` don't see AZU-0013/0022 (public KV/Postgres), AZU-0019/0024 (missing Postgres audit logging), or AZU-0015 (unclassified KV secrets). The infra-reviewer agent is good for judgement calls but doesn't apply Aqua's curated rule library. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 4 +- .../cloud-infra/.claude-plugin/plugin.json | 6 +- plugins/cloud-infra/README.md | 5 +- plugins/cloud-infra/agents/infra-reviewer.md | 12 ++ .../skills/complete-infra/SKILL.md | 16 +- .../cloud-infra/skills/infra-lint/SKILL.md | 13 ++ .../cloud-infra/skills/trivy-scan/SKILL.md | 148 ++++++++++++++++++ .../skills/trivy-scan/scripts/scan-trivy.sh | 110 +++++++++++++ 8 files changed, 306 insertions(+), 8 deletions(-) create mode 100644 plugins/cloud-infra/skills/trivy-scan/SKILL.md create mode 100755 plugins/cloud-infra/skills/trivy-scan/scripts/scan-trivy.sh diff --git a/README.md b/README.md index 69cc9eb..c7f716b 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![Plugins](https://img.shields.io/badge/plugins-4-purple)](plugins/) -[![Skills](https://img.shields.io/badge/skills-29-green)](plugins/) +[![Skills](https://img.shields.io/badge/skills-30-green)](plugins/) [![Agents](https://img.shields.io/badge/agents-6-orange)](plugins/) [![.NET 10](https://img.shields.io/badge/.NET-10-512BD4)](https://dotnet.microsoft.com/) [![React 19](https://img.shields.io/badge/React-19-61DAFB)](https://react.dev/) [![Terraform](https://img.shields.io/badge/Terraform-IaC-7B42BC)](https://www.terraform.io/) -> AI-powered full-stack cloud engineer for Claude Code. 29 skills, 6 agents, 14 rules across 4 plugins. +> AI-powered full-stack cloud engineer for Claude Code. 30 skills, 6 agents, 14 rules across 4 plugins. ``` /plugin marketplace add makigjuro/cloudstack-ai-plugins diff --git a/plugins/cloud-infra/.claude-plugin/plugin.json b/plugins/cloud-infra/.claude-plugin/plugin.json index 7541fce..4db63c2 100644 --- a/plugins/cloud-infra/.claude-plugin/plugin.json +++ b/plugins/cloud-infra/.claude-plugin/plugin.json @@ -1,12 +1,12 @@ { "name": "cloud-infra", - "description": "Terraform + Helm infrastructure scaffolding, linting, and review — module scaffolding, chart templates, validation pipelines, and infrastructure-specific code review.", - "version": "0.1.0", + "description": "Terraform + Helm infrastructure scaffolding, linting, and review — module scaffolding, chart templates, validation pipelines, security scanning (trivy), and infrastructure-specific code review.", + "version": "0.2.0", "author": { "name": "Marjan Gjuroski" }, "license": "MIT", "homepage": "https://github.com/makigjuro/cloudstack-ai-plugins#cloud-infrastructure", "repository": "https://github.com/makigjuro/cloudstack-ai-plugins", - "keywords": ["terraform", "helm", "azure", "kubernetes", "aks", "infrastructure-as-code", "terragrunt"] + "keywords": ["terraform", "helm", "azure", "kubernetes", "aks", "infrastructure-as-code", "terragrunt", "trivy", "security-scanning"] } diff --git a/plugins/cloud-infra/README.md b/plugins/cloud-infra/README.md index 865c72c..160dee6 100644 --- a/plugins/cloud-infra/README.md +++ b/plugins/cloud-infra/README.md @@ -10,10 +10,11 @@ A Claude Code plugin for cloud infrastructure management — Terraform modules, |-------|-------------| | `/add-terraform-module` | Scaffold a new Terraform module with variables, main, outputs, and optional IaC wrapper wiring | | `/add-helm-chart` | Scaffold a new Helm chart with standard Kubernetes templates and values | -| `/infra-lint` | Lint Terraform and Helm charts only — fast, skips application/frontend checks | +| `/infra-lint` | Lint Terraform and Helm charts only — fast, skips application/frontend checks. Runs `/trivy-scan` as its final step | +| `/trivy-scan` | Scan Terraform and Helm charts with [trivy](https://trivy.dev) for security misconfigurations (public access defaults, weak TLS, over-broad IAM). Honours `.trivyignore` with justifying comments | | `/infra-apply` | Run `terraform plan` for review and optionally apply infrastructure changes | | `/infra-plan` | Plan infrastructure changes with dependency analysis and risk assessment | -| `/complete-infra` | Full infrastructure completion workflow — lint, validate, review agent, and PR creation | +| `/complete-infra` | Full infrastructure completion workflow — parallel lint + security scan, review agent, and PR creation | ### Rules diff --git a/plugins/cloud-infra/agents/infra-reviewer.md b/plugins/cloud-infra/agents/infra-reviewer.md index 26f2eb1..d638697 100644 --- a/plugins/cloud-infra/agents/infra-reviewer.md +++ b/plugins/cloud-infra/agents/infra-reviewer.md @@ -27,6 +27,7 @@ Ignore all other changes — the general `reviewer` agent handles application co 1. Get the diff against the base branch for infrastructure files 2. Get changed file list for infrastructure directories 3. Review each changed file against the checklists below +4. If a `.trivyignore` is present at `{TF_PARENT}/.trivyignore`, read it and correlate its suppressions with the current diff (see **Scanner Correlation** below) ## Terraform Checklist @@ -82,6 +83,17 @@ Ignore all other changes — the general `reviewer` agent handles application co - `runAsNonRoot: true` where possible - No secrets in plain `values.yaml` — use sealed secrets or external secrets +## Scanner Correlation (trivy) + +The `/complete-infra` and `/infra-lint` skills run `/trivy-scan` as a separate gate, so your job here is NOT to re-run trivy — it's to sanity-check the human decisions captured in the ignore file. + +If `{TF_PARENT}/.trivyignore` exists, read it and confirm: + +- **Every suppression has a justifying comment.** An uncommented rule ID is a red flag — either the justification was never written, or the ignore was added reflexively to silence CI. Flag these as warnings. +- **The justification is consistent with the current diff.** If a comment says "deferred until VNet integration" but the current diff *adds* VNet integration, the suppression is now obsolete — flag it. +- **The rule is still relevant.** Some trivy rules target deprecated resource types (e.g. `AZU-0026` was written for Azure PostgreSQL Single Server; it misfires on Flexible Server). Confirm the justification actually matches the current resource usage. +- **Don't re-raise suppressed findings as blockers** — the team has already made a conscious decision on them. + ## CI/CD Checklist ### Secrets diff --git a/plugins/cloud-infra/skills/complete-infra/SKILL.md b/plugins/cloud-infra/skills/complete-infra/SKILL.md index 678eea6..23c10c1 100644 --- a/plugins/cloud-infra/skills/complete-infra/SKILL.md +++ b/plugins/cloud-infra/skills/complete-infra/SKILL.md @@ -43,6 +43,11 @@ These scripts read `TF_PATH` and `CHARTS_PATH` from environment variables. Expor export TF_PATH="..." CHARTS_PATH="..." ``` +The security scan (Track D below) reuses the script from the `trivy-scan` skill rather than duplicating it: +```bash +bash {plugin-skills-path}/trivy-scan/scripts/scan-trivy.sh +``` + ## Process ### Phase 0: Pre-flight @@ -85,7 +90,15 @@ export CHARTS_PATH="{CHARTS_PATH}" bash {plugin-skills-path}/complete-infra/scripts/lint-helm.sh ``` -If any lint track fails, STOP and report. +**Track D: Security scan (trivy)** +```bash +export TF_PARENT="{TF_PARENT}" CHARTS_PATH="{CHARTS_PATH}" +bash {plugin-skills-path}/trivy-scan/scripts/scan-trivy.sh +``` + +Honours `.trivyignore` at `{TF_PARENT}/.trivyignore`. Every suppression must carry a justifying comment — see the `trivy-scan` skill for conventions. + +If any lint or scan track fails, STOP and report. Treat `trivy` missing as a FAIL and print the install hint so the user can remediate. ### Phase 2: Infrastructure Review (Parallel Agents) @@ -119,6 +132,7 @@ Run `/create-pr {issue}`. - Terraform Format: PASS - Terraform Validate: PASS - Helm Lint: PASS / SKIP (no chart changes) +- Security Scan (trivy): PASS / WARN (findings in .trivyignore only) / FAIL - Infra Review: PASS PR is ready for human review. diff --git a/plugins/cloud-infra/skills/infra-lint/SKILL.md b/plugins/cloud-infra/skills/infra-lint/SKILL.md index 80228c1..bbe2027 100644 --- a/plugins/cloud-infra/skills/infra-lint/SKILL.md +++ b/plugins/cloud-infra/skills/infra-lint/SKILL.md @@ -66,6 +66,18 @@ done If `{CHARTS_PATH}/` doesn't exist, skip Helm linting and note it. +## Security Scan + +After lint passes, invoke the `trivy-scan` skill as the final verification step. This catches security misconfigurations that `terraform validate` and `helm lint` don't see (public access defaults, weak TLS, over-broad IAM, missing encryption). + +``` +Skill(skill="cloud-infra:trivy-scan") +``` + +Findings suppressed by a committed `.trivyignore` (with justifying comments) don't block — only unsuppressed CRITICAL/HIGH/MEDIUM fail the gate. + +If trivy isn't installed, the skill prints the install command and exits non-zero. Treat that as a FAIL for this skill's output but print the install hint so the user can remediate. + ## Output Report pass/fail per category: @@ -75,4 +87,5 @@ Report pass/fail per category: - Terragrunt Validate: PASS/FAIL (or SKIPPED if IAC_WRAPPER != terragrunt) - Helm Lint: PASS/FAIL (or SKIPPED) - Helm Template: PASS/FAIL (or SKIPPED) +- Security Scan (trivy): PASS/FAIL (or SKIPPED if trivy missing) ``` diff --git a/plugins/cloud-infra/skills/trivy-scan/SKILL.md b/plugins/cloud-infra/skills/trivy-scan/SKILL.md new file mode 100644 index 0000000..e4b1b8b --- /dev/null +++ b/plugins/cloud-infra/skills/trivy-scan/SKILL.md @@ -0,0 +1,148 @@ +--- +name: trivy-scan +description: Scan Terraform and Helm charts for security misconfigurations using trivy. Use for a fast IaC security check — catches issues that `/infra-lint` doesn't (secrets, public-access defaults, missing encryption, weak TLS, over-broad IAM). Invoked automatically by `/infra-lint` and `/complete-infra`. +allowed-tools: Bash, Read +user-invocable: true +--- + +# Trivy Security Scan + +Run Aqua Security's trivy against the infrastructure tree to catch misconfigurations that `terraform validate` and `helm lint` miss — things like publicly exposed storage, missing encryption, weak TLS, over-broad IAM role assignments, and container image vulnerabilities. + +## When to Use + +- **Directly (`/trivy-scan`)** — manual scan, e.g. while iterating on a module or before a PR. +- **Indirectly** — invoked from `/infra-lint` (fast check) and `/complete-infra` (pre-PR verification gate). + +## Arguments + +- `--strict` — Fail on any finding, including `LOW`. Default: fail on `CRITICAL`, `HIGH`, `MEDIUM`; `LOW` is advisory. +- `--warn-only` — Never exit non-zero; just report. Useful for CI `continue-on-error` style gating. +- `--severity HIGH,CRITICAL` — Comma-separated list to override the default severity filter. + +## Configuration + +Read `cloudstack.json` from the project root at start of execution. Extract: +- `TF_PATH` = `infrastructure.terraformPath` (default: `infra/terraform/modules`) +- `CHARTS_PATH` = `infrastructure.chartsPath` (default: `deploy/charts`) + +Derive `TF_PARENT` as the parent directory of `TF_PATH` (e.g., `infra/terraform/modules` → `infra/terraform`). If `cloudstack.json` is missing, auto-detect by scanning the project. + +## Prerequisites + +`trivy` must be installed locally. The skill does NOT auto-install — be explicit about system changes. + +```bash +# macOS +brew install trivy + +# Linux +curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin +``` + +If `trivy` is missing, the skill prints the install command and exits non-zero (unless `--warn-only`). + +## Suppressing findings — `.trivyignore` + +Project-local exemptions go in `deploy/terraform/.trivyignore` (or the parent of `TF_PATH`). One rule ID per line, with a comment explaining **why**: + +``` +# Deferred until VNet integration (see deploy/terraform/README.md "Known limitations") +AZU-0013 +AZU-0022 + +# Trivy check written for deprecated Single Server; we run Flexible Server +# and set TLS via `require_secure_transport` + `ssl_min_protocol_version` +AZU-0026 +``` + +Every ignore entry MUST carry a justification comment. The comment is what a future reviewer uses to decide whether the suppression is still valid. + +## Scripts + +| Script | Purpose | +|--------|---------| +| `scripts/scan-trivy.sh` | Runs trivy against `TF_PARENT` and `CHARTS_PATH`, honouring `.trivyignore` | + +The script reads `TF_PARENT` and `CHARTS_PATH` from environment variables. Export them before calling: + +```bash +export TF_PARENT="..." CHARTS_PATH="..." +bash {plugin-skills-path}/trivy-scan/scripts/scan-trivy.sh +``` + +## Process + +### Step 1: Pre-flight + +Verify `trivy` is available: + +```bash +if ! command -v trivy >/dev/null 2>&1; then + echo "trivy is not installed. Install it and re-run:" + echo " brew install trivy # macOS" + echo " curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin # Linux" + exit 1 +fi +``` + +### Step 2: Run the scan + +```bash +export TF_PARENT="{TF_PARENT}" CHARTS_PATH="{CHARTS_PATH}" +bash {plugin-skills-path}/trivy-scan/scripts/scan-trivy.sh +``` + +### Step 3: Report + +Group findings by severity. For each finding, include: +- Rule ID (e.g. `AZU-0013`) +- Severity +- Title +- File:line reference +- Remediation link (trivy provides this) + +If findings exist that are NOT in `.trivyignore`, STOP with FAIL. If all remaining findings are suppressed by `.trivyignore`, PASS with a summary of the suppressed count. + +## Output + +``` +## Trivy Scan: {branch-name} + +**Scanned:** {TF_PARENT}, {CHARTS_PATH} + +### Findings (excluding .trivyignore suppressions) + +| Severity | Rule | Title | File | +|----------|------|-------|------| +| CRITICAL | AZU-0041 | Storage account uses outdated TLS version | modules/storage/main.tf | + +### Suppressed (.trivyignore) + +| Rule | Justification | +|------|---------------| +| AZU-0013 | Deferred until VNet integration | + +### Summary + +| Severity | Count | +|----------|-------| +| CRITICAL | 1 | +| HIGH | 0 | +| MEDIUM | 0 | +| LOW | 0 (not shown; pass --strict to include) | + +**Overall: PASS / FAIL** +``` + +## When NOT to Use + +- **Runtime container scanning** — `/trivy-scan` only does IaC (`trivy config`). For image scanning (`trivy image`) use a separate CI step. +- **Policy-as-code beyond trivy's built-in rules** — for custom OPA/Rego checks, use a dedicated tool. + +## Guidelines + +- Never commit a `.trivyignore` entry without a justification comment. +- Periodically review `.trivyignore` — deferred items should either be resolved or have an issue tracking the work. +- Treat `CRITICAL`/`HIGH` findings as PR blockers; `MEDIUM` as must-fix-before-merge; `LOW` as advisory. +- Trivy rules occasionally target deprecated resource types (e.g. AZU-0026 is Single-Server only). Verify before suppressing — and document the reason. diff --git a/plugins/cloud-infra/skills/trivy-scan/scripts/scan-trivy.sh b/plugins/cloud-infra/skills/trivy-scan/scripts/scan-trivy.sh new file mode 100755 index 0000000..d379a91 --- /dev/null +++ b/plugins/cloud-infra/skills/trivy-scan/scripts/scan-trivy.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +# Scan Terraform + Helm charts for security misconfigurations using trivy. +# +# Reads: +# TF_PARENT — Terraform tree root (default: infra/terraform) +# CHARTS_PATH — Helm charts root (default: deploy/charts) +# TRIVY_SEVERITY — comma-list of severities to fail on +# (default: CRITICAL,HIGH,MEDIUM) +# TRIVY_STRICT — "1" to include LOW in the fail set +# TRIVY_WARN_ONLY — "1" to never exit non-zero +# TRIVY_IGNOREFILE — path to .trivyignore (default: {TF_PARENT}/.trivyignore +# if present, else {CHARTS_PATH}/../.trivyignore) +# +# Usage: +# TF_PARENT=infra/terraform CHARTS_PATH=deploy/charts bash scan-trivy.sh +# +# Exit codes: +# 0 clean (or --warn-only) — PR can proceed +# 1 findings at failing severity — PR blocked +# 2 trivy not installed / invocation error + +set -uo pipefail + +TF_PARENT="${TF_PARENT:-infra/terraform}" +CHARTS_PATH="${CHARTS_PATH:-deploy/charts}" +TRIVY_SEVERITY="${TRIVY_SEVERITY:-CRITICAL,HIGH,MEDIUM}" +TRIVY_STRICT="${TRIVY_STRICT:-0}" +TRIVY_WARN_ONLY="${TRIVY_WARN_ONLY:-0}" + +if [ "$TRIVY_STRICT" = "1" ]; then + TRIVY_SEVERITY="CRITICAL,HIGH,MEDIUM,LOW" +fi + +# --- preflight --- +if ! command -v trivy >/dev/null 2>&1; then + cat >&2 <