tutorial

Monitoring Terrascan IaC Security Scans with Vigilmon

Track Terrascan policy violation trends, scan health, and Terraform/Kubernetes security posture using Vigilmon — get alerts when scans fail, violations spike, or coverage drops.

Infrastructure-as-code security scanning is only as good as its reliability. Terrascan checks Terraform, Kubernetes manifests, Helm charts, and Dockerfiles against hundreds of security policies before they reach production — catching misconfigurations like exposed S3 buckets, overprivileged IAM roles, and containers running as root before a single resource is provisioned.

But Terrascan needs the same oversight it provides to your infrastructure. When scan jobs fail silently, when policy sets stop updating, or when violation counts spike without anyone noticing, your IaC security layer develops blind spots. Teams discover these gaps during post-incident reviews — which is the worst possible time.

This tutorial shows how to integrate Vigilmon with Terrascan to monitor scan continuity, policy health, and violation trends across your infrastructure code.

What You'll Build

  • Heartbeat monitors confirming Terrascan runs on schedule
  • Webhook-based violation trend reporting to Vigilmon
  • HTTP monitors for Terrascan's API server (if running in server mode)
  • Alert routing for scan failures versus policy violations

Prerequisites

  • A Vigilmon account at vigilmon.online
  • A Vigilmon API key (Settings → API Keys)
  • Terrascan installed (brew install terrascan or download from GitHub releases)
  • A Terraform or Kubernetes project to scan
  • A CI/CD system (GitHub Actions, GitLab CI, or similar)

Step 1: Run Terrascan and Capture JSON Output

Start with a baseline scan that captures structured output for Vigilmon to consume.

# Scan a Terraform directory
terrascan scan \
  --iac-type terraform \
  --policy-type aws \
  --output json \
  --log-type json \
  > terrascan-results.json 2>&1

echo "Exit code: $?"

Terrascan returns non-zero exit codes for policy violations by default. This is correct behavior for enforcement, but in a monitoring context you want to capture results even when violations exist. Use || true or capture the exit code explicitly:

terrascan scan \
  --iac-type terraform \
  --policy-type aws \
  --output json \
  --non-recursive \
  . > terrascan-results.json 2>&1 ; SCAN_EXIT=$?

echo "Scan complete with exit code: $SCAN_EXIT"

The JSON output includes a results block with violations, passed_rules, skipped_rules, and scan_errors.


Step 2: Create a Heartbeat Monitor for Scan Continuity

The most important signal is whether Terrascan is running at all. A heartbeat monitor catches the case where a CI change disables the scan step, a credential expires, or a runner goes offline.

In Vigilmon, go to Monitors → New Monitor → Heartbeat:

  • Name: Terrascan - Terraform Scan
  • Expected interval: 24 hours
  • Grace period: 4 hours

Vigilmon gives you a unique heartbeat URL. Add it to the end of your scan job:

# .github/workflows/terrascan.yml
name: Terrascan IaC Security Scan

on:
  push:
    branches: [main]
    paths:
      - '**.tf'
      - '**.yaml'
      - '**.yml'
  schedule:
    - cron: '0 4 * * *'  # Daily baseline at 4am UTC

jobs:
  terrascan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Terrascan
        run: |
          curl -L "$(curl -s https://api.github.com/repos/tenable/terrascan/releases/latest \
            | jq -r '.assets[] | select(.name | contains("Linux_x86_64")) | .browser_download_url')" \
            | tar -xz terrascan
          sudo mv terrascan /usr/local/bin/

      - name: Run Terrascan
        id: scan
        run: |
          terrascan scan \
            --iac-type terraform \
            --output json \
            . > terrascan-results.json 2>&1 || true

      - name: Ping Vigilmon heartbeat
        if: always()
        run: |
          curl -s -X POST "${{ secrets.VIGILMON_TERRASCAN_HEARTBEAT_URL }}"

Using if: always() ensures the heartbeat fires even if Terrascan finds violations (which exits non-zero). The heartbeat confirms the scan ran — separate logic handles whether violations are acceptable.


Step 3: Report Violation Metrics to Vigilmon

Violations are expected — the goal is tracking their trend over time and alerting on unexpected spikes.

#!/bin/bash
# report-terrascan-metrics.sh

RESULTS_FILE="${1:-terrascan-results.json}"
VIGILMON_WEBHOOK="${2:-$VIGILMON_WEBHOOK_URL}"

if [ ! -f "$RESULTS_FILE" ]; then
  echo "ERROR: Results file not found: $RESULTS_FILE"
  curl -s -X POST "$VIGILMON_WEBHOOK" \
    -H "Content-Type: application/json" \
    -d '{"status": "down", "message": "Terrascan results file missing — scan may have crashed"}'
  exit 1
fi

# Parse violation counts by severity
HIGH=$(jq '[.results.violations[] | select(.severity == "HIGH")] | length' "$RESULTS_FILE" 2>/dev/null || echo 0)
MEDIUM=$(jq '[.results.violations[] | select(.severity == "MEDIUM")] | length' "$RESULTS_FILE" 2>/dev/null || echo 0)
LOW=$(jq '[.results.violations[] | select(.severity == "LOW")] | length' "$RESULTS_FILE" 2>/dev/null || echo 0)
PASSED=$(jq '.results.passed_rules | length' "$RESULTS_FILE" 2>/dev/null || echo 0)
ERRORS=$(jq '.results.scan_errors | length' "$RESULTS_FILE" 2>/dev/null || echo 0)

TOTAL_VIOLATIONS=$((HIGH + MEDIUM + LOW))

# Determine status
STATUS="healthy"
if [ "$ERRORS" -gt 0 ]; then
  STATUS="down"
elif [ "$HIGH" -gt 0 ]; then
  STATUS="degraded"
fi

MESSAGE="Terrascan: ${TOTAL_VIOLATIONS} violations (HIGH:${HIGH} MED:${MEDIUM} LOW:${LOW}), ${PASSED} passed, ${ERRORS} errors"

curl -s -X POST "$VIGILMON_WEBHOOK" \
  -H "Content-Type: application/json" \
  -d "{
    \"status\": \"$STATUS\",
    \"message\": \"$MESSAGE\",
    \"metadata\": {
      \"violations_high\": $HIGH,
      \"violations_medium\": $MEDIUM,
      \"violations_low\": $LOW,
      \"passed_rules\": $PASSED,
      \"scan_errors\": $ERRORS
    }
  }"

echo "Reported to Vigilmon: $MESSAGE"

Add this to your CI workflow:

      - name: Report Terrascan metrics to Vigilmon
        if: always()
        env:
          VIGILMON_WEBHOOK_URL: ${{ secrets.VIGILMON_WEBHOOK_URL }}
        run: |
          chmod +x ./scripts/report-terrascan-metrics.sh
          ./scripts/report-terrascan-metrics.sh terrascan-results.json "$VIGILMON_WEBHOOK_URL"

Step 4: Monitor Terrascan in Server Mode

Terrascan can run as an admission webhook server for Kubernetes, validating resources before they're applied to the cluster. In this mode, availability monitoring becomes critical — if the Terrascan admission webhook goes down, Kubernetes will either block all deployments or silently skip scanning, depending on your failurePolicy setting.

Start Terrascan in server mode:

terrascan server \
  --port 9010 \
  --cert-path /certs/tls.crt \
  --key-path /certs/tls.key

Add a Vigilmon HTTP monitor for the server health endpoint:

  • Name: Terrascan Admission Webhook Server
  • URL: http://terrascan-service.security.svc.cluster.local:9010/healthz
  • Interval: 1 minute
  • Expected status: 200
  • Alert: PagerDuty (this is production-critical — a down webhook blocks or bypasses all deploys)

For external monitoring, expose the health endpoint through an ingress:

# k8s/terrascan-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: terrascan-health
  namespace: security
spec:
  rules:
    - host: terrascan-health.internal.example.com
      http:
        paths:
          - path: /healthz
            pathType: Prefix
            backend:
              service:
                name: terrascan
                port:
                  number: 9010

Then monitor https://terrascan-health.internal.example.com/healthz from Vigilmon.


Step 5: Policy Set Currency Monitoring

Terrascan policy sets are versioned. Outdated policies miss newly discovered misconfigurations. Check that your policy version matches the latest release and alert when you're falling behind.

#!/bin/bash
# check-terrascan-policy-version.sh

INSTALLED_VERSION=$(terrascan version 2>/dev/null | grep -oP 'v\d+\.\d+\.\d+' | head -1)
LATEST_VERSION=$(curl -s https://api.github.com/repos/tenable/terrascan/releases/latest \
  | jq -r '.tag_name')

if [ "$INSTALLED_VERSION" != "$LATEST_VERSION" ]; then
  echo "Policy version mismatch: installed=$INSTALLED_VERSION latest=$LATEST_VERSION"
  
  curl -s -X POST "$VIGILMON_WEBHOOK_URL" \
    -H "Content-Type: application/json" \
    -d "{
      \"status\": \"degraded\",
      \"message\": \"Terrascan outdated: $INSTALLED_VERSION vs latest $LATEST_VERSION\",
      \"metadata\": {
        \"installed\": \"$INSTALLED_VERSION\",
        \"latest\": \"$LATEST_VERSION\"
      }
    }"
else
  echo "Terrascan is current: $INSTALLED_VERSION"
fi

Run this as a weekly scheduled check:

  check-policy-currency:
    runs-on: ubuntu-latest
    steps:
      - name: Check Terrascan policy version
        env:
          VIGILMON_WEBHOOK_URL: ${{ secrets.VIGILMON_WEBHOOK_URL }}
        run: |
          chmod +x ./scripts/check-terrascan-policy-version.sh
          ./scripts/check-terrascan-policy-version.sh

Step 6: Multi-IaC Coverage Monitoring

If your organization uses both Terraform and Kubernetes manifests, run separate scans and heartbeats for each — a single heartbeat masks which type of scan failed.

# Terraform scan heartbeat
- name: Ping Terraform scan heartbeat
  if: always()
  run: curl -s "${{ secrets.VIGILMON_TF_HEARTBEAT_URL }}"

# Kubernetes manifest scan
- name: Run Kubernetes scan
  run: |
    terrascan scan --iac-type k8s --output json . > terrascan-k8s.json 2>&1 || true

- name: Ping Kubernetes scan heartbeat
  if: always()
  run: curl -s "${{ secrets.VIGILMON_K8S_HEARTBEAT_URL }}"

Create separate Vigilmon monitors for each scan type with distinct alert channels. Kubernetes admission webhook failures warrant a PagerDuty escalation; Terraform scan gaps can go to a Slack channel.


Monitoring Coverage Summary

| What to monitor | Monitor type | Interval | Alert on | |---|---|---|---| | Terraform scan running | Heartbeat | 24h | Missed ping | | K8s manifest scan running | Heartbeat | 24h | Missed ping | | Admission webhook server | HTTP | 1 min | Non-200 or down | | High severity violations | Webhook | Per scan | COUNT > 0 | | Scan errors | Webhook | Per scan | ERRORS > 0 | | Policy version currency | Script + webhook | Weekly | Version mismatch |


Terrascan is a policy enforcement tool. When it fails to run or runs against outdated policies, your infrastructure deployments proceed without security checks — the opposite of what you intended. Vigilmon closes the observability gap, ensuring your IaC security layer is as reliable as the infrastructure it protects.

Start monitoring your security pipeline free at vigilmon.online


Tags: #terrascan #iac #security #terraform #kubernetes #monitoring

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →