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 terrascanor 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