Skip to content

🤖 ci: add terraform lint and security scanning#33

Merged
ThomasK33 merged 2 commits into
mainfrom
terraform-security-6sy7
Feb 10, 2026
Merged

🤖 ci: add terraform lint and security scanning#33
ThomasK33 merged 2 commits into
mainfrom
terraform-security-6sy7

Conversation

@ThomasK33

Copy link
Copy Markdown
Member

Summary

Add Terraform quality and security checks to CI using path-based gating and SHA-pinned actions.

Background

The repository has a Terraform root module under terraform/, but CI previously did not run Terraform validation or IaC security scanning. This change ensures Terraform-specific regressions are caught during PR validation.

Implementation

  • Extended the changes job in .github/workflows/ci.yaml with a terraform output and path filter.
  • Added a new terraform CI job that runs:
    • terraform fmt -check -diff -recursive
    • terraform init -backend=false -lockfile=readonly -input=false
    • terraform validate -no-color
    • tflint --init and tflint --recursive
    • Trivy config scan for HIGH,CRITICAL severities
  • Added terraform/.tflint.hcl to enable the AWS ruleset plugin.
  • Added terraform/.trivyignore with documented, intentional sandbox-risk exceptions used by CI.

Validation

  • make verify-vendor
  • make test
  • make build
  • make lint
  • go tool actionlint
  • terraform -chdir=terraform fmt -check -diff -recursive
  • terraform -chdir=terraform init -backend=false -lockfile=readonly -input=false
  • terraform -chdir=terraform validate -no-color
  • cd terraform && tflint --init && tflint --recursive
  • docker run --rm -v "$(pwd):/work" -w /work aquasec/trivy:0.65.0 config terraform/ --severity HIGH,CRITICAL --ignorefile terraform/.trivyignore --exit-code 1

Risks

  • False negatives risk (low/moderate): .trivyignore suppresses selected findings for sandbox-intended settings; future production-hardening should revisit these ignores.
  • CI runtime risk (low): adds one new job, increasing CI time when Terraform paths change.

📋 Implementation Plan

Plan: Add Terraform linting + security scanning to CI

Context / Why

This repo contains a Terraform root module under terraform/ (AWS VPC + EKS + IAM). Today, CI does not run any Terraform checks, so formatting drift, config errors, or obvious security misconfigurations can land unnoticed.

Goal: integrate three tools into .github/workflows/ci.yaml in a way that matches the repo’s existing CI conventions (path-based job gating and SHA-pinned actions):

  1. terraform fmt -check + terraform validate
  2. TFLint (with AWS ruleset)
  3. Trivy config (IaC misconfiguration scan; includes tfsec rules)

Evidence (what was checked)

  • .github/workflows/ci.yaml: CI uses a changes job with dorny/paths-filter outputs (go, workflows, publish) to gate expensive jobs; actions are SHA-pinned.
  • terraform/versions.tf: Terraform required_version = ">= 1.11" and uses an s3 backend (bucket/key provided at init time), so CI must run terraform init -backend=false.
  • terraform/variables.tf: defaults exist for variables (e.g. aws_region), so terraform validate can run in CI without extra inputs.
  • terraform/.terraform.lock.hcl: provider lockfile exists; CI should keep it immutable.

Implementation details

1) Extend the changes job to detect Terraform/IaC changes

File: .github/workflows/ci.yaml

Edits:

  • Add a new terraform output alongside go/workflows/publish.
  • Set merge-queue defaults so merge groups always run the Terraform job.
  • Add a terraform: filter that matches Terraform code and Terraform-scanner config files.

Shape of change (illustrative snippet):

jobs:
  changes:
    ...
    outputs:
      go: ${{ steps.merge_group_defaults.outputs.go || steps.filter.outputs.go }}
      workflows: ${{ steps.merge_group_defaults.outputs.workflows || steps.filter.outputs.workflows }}
      publish: ${{ steps.merge_group_defaults.outputs.publish || steps.filter.outputs.publish }}
      terraform: ${{ steps.merge_group_defaults.outputs.terraform || steps.filter.outputs.terraform }}

    steps:
      - name: Set merge-group defaults
        if: github.event_name == 'merge_group'
        id: merge_group_defaults
        run: |
          {
            echo "go=true"
            echo "workflows=true"
            echo "publish=false"
            echo "terraform=true"
          } >> "$GITHUB_OUTPUT"

      - name: Detect changed paths
        ...
        with:
          filters: |
            ...
            terraform:
              - 'terraform/**'
              - '.github/workflows/ci.yaml' # ensures terraform job runs when its CI definition changes
Why include .github/workflows/ci.yaml in the terraform filter?

If the Terraform job is gated strictly on terraform/**, then PRs that only modify the CI job definition could skip the Terraform job entirely. Including ci.yaml makes Terraform checks run when their own CI wiring changes, which catches breakage early.

2) Add a dedicated Terraform CI job

File: .github/workflows/ci.yaml

Add a new job (e.g. terraform) that:

  • is gated by needs.changes.outputs.terraform == 'true' (or merge-group)
  • runs on depot-ubuntu-24.04
  • uses SHA-pinned actions consistent with the repo

Recommended steps (in order):

  1. Checkout
  2. Install Terraform (pin to a compatible version)
  3. terraform fmt -check -diff -recursive
  4. terraform init -backend=false -lockfile=readonly (avoid S3 and ensure lockfile isn’t modified)
  5. terraform validate -no-color
  6. Install TFLint + run tflint --init then tflint --recursive
  7. Run Trivy IaC scan on terraform/

Job skeleton (illustrative; actions must be SHA-pinned like the rest of the repo):

  terraform:
    name: Terraform (fmt/validate/tflint/trivy)
    needs: changes
    if: github.event_name == 'merge_group' || (needs.changes.outputs.terraform == 'true' && (github.event_name != 'push' || github.actor != 'github-merge-queue[bot]'))
    runs-on: depot-ubuntu-24.04
    timeout-minutes: 15
    permissions:
      contents: read
    steps:
      - name: Checkout
        uses: actions/checkout@<sha> # keep SHA-pinned
        with:
          persist-credentials: false

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@<sha>
        with:
          # Pin an explicit patch version that satisfies terraform/versions.tf (>= 1.11).
          terraform_version: "1.11.0"

      - name: terraform fmt (check)
        working-directory: terraform
        run: terraform fmt -check -diff -recursive

      - name: terraform init (backend disabled)
        working-directory: terraform
        run: terraform init -backend=false -lockfile=readonly -input=false

      - name: terraform validate
        working-directory: terraform
        run: terraform validate -no-color

      - name: Setup TFLint
        uses: terraform-linters/setup-tflint@<sha>
        with:
          tflint_version: v0.XX.X

      - name: tflint
        working-directory: terraform
        run: |
          tflint --init
          tflint --recursive

      - name: Trivy config scan (Terraform)
        uses: aquasecurity/trivy-action@<sha>
        with:
          scan-type: config
          scan-ref: terraform/
          severity: HIGH,CRITICAL
          exit-code: 1

Notes / defensive choices:

  • Use -lockfile=readonly so CI fails if a PR changes provider constraints but forgets to update terraform/.terraform.lock.hcl.
  • Keep Trivy’s failure threshold to HIGH,CRITICAL initially to avoid blocking on low-signal findings.

3) Add minimal TFLint configuration for the AWS ruleset

File: terraform/.tflint.hcl

Add a small config enabling the AWS ruleset plugin.

Starting point:

plugin "aws" {
  enabled = true
  version = "0.XX.X"
  source  = "github.com/terraform-linters/tflint-ruleset-aws"

  # Optional: make region-aware checks deterministic (align with terraform/variables.tf).
  region = "eu-central-1"
}
Version pinning guidance
  • Pin Terraform, TFLint, and the tflint-ruleset-aws plugin to explicit versions.
  • In workflows, follow this repo’s convention of uses: org/action@<commit-sha> # vX.Y.Z.
  • When implementing, obtain action SHAs from each action’s GitHub release/tag page.

4) Handle initial Trivy findings without blocking development

Trivy is likely to flag common EKS/VPC issues (e.g., public API endpoint enabled, missing encryption, permissive IAM). Decide per-finding:

  • Fix in Terraform (preferred)
  • Accept & document via .trivyignore (only for justified false positives / accepted risk)

If needed: add a terraform/.trivyignore and reference it explicitly in the Trivy step.

5) Validation (local + CI)

Before merging the CI integration, run the same commands locally to confirm behavior:

  • terraform -chdir=terraform fmt -check -diff -recursive
  • terraform -chdir=terraform init -backend=false -lockfile=readonly -input=false
  • terraform -chdir=terraform validate -no-color
  • tflint from within terraform/ (ensure plugin init succeeds)
  • trivy config terraform/ --severity HIGH,CRITICAL --exit-code 1

Acceptance criteria

  • CI runs a Terraform job when terraform/** (or the Terraform CI wiring) changes.
  • The Terraform job fails on:
    • formatting drift (terraform fmt -check)
    • invalid config (terraform validate)
    • TFLint violations
    • Trivy findings at HIGH/CRITICAL severity
  • Actions are SHA-pinned and consistent with existing workflow style.

Generated with mux • Model: openai:gpt-5.3-codex • Thinking: xhigh • Cost: $0.27

Add Terraform-specific CI coverage gated by changed paths:
- terraform fmt and validate
- tflint with AWS ruleset plugin
- trivy config scanning at HIGH/CRITICAL severity

Also add local scanner configuration files under terraform/.

---

_Generated with `mux` • Model: `openai:gpt-5.3-codex` • Thinking: `xhigh` • Cost: `$0.27`_

<!-- mux-attribution: model=openai:gpt-5.3-codex thinking=xhigh costs=0.27 -->
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 889dc84267

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/ci.yaml
Require the terraform CI job for publish-main so GHCR image publishes on
main are blocked whenever Terraform validation/scanning fails.

---

_Generated with `mux` • Model: `openai:gpt-5.3-codex` • Thinking: `xhigh` • Cost: `$0.27`_

<!-- mux-attribution: model=openai:gpt-5.3-codex thinking=xhigh costs=0.27 -->
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33
ThomasK33 added this pull request to the merge queue Feb 10, 2026
@ThomasK33

Copy link
Copy Markdown
Member Author

Merged via the queue into main with commit ce23fa8 Feb 10, 2026
8 checks passed
@ThomasK33
ThomasK33 deleted the terraform-security-6sy7 branch February 10, 2026 13:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant