1+ name : Vulnerability Scan Daily
2+ on :
3+ schedule :
4+ - cron : ' 0 0 * * *'
5+
6+ jobs :
7+ build :
8+ name : Build and scan image
9+ runs-on : ubuntu-latest
10+ env :
11+ DOCKER_IMAGE : newrelic/infrastructure-bundle
12+ DOCKER_IMAGE_TAG : ci
13+ TRIVY_DISABLE_VEX_NOTICE : " true"
14+ steps :
15+ - uses : actions/checkout@v4
16+
17+ - name : Set up QEMU
18+ uses : docker/setup-qemu-action@v3
19+ - name : Set up Docker Buildx
20+ uses : docker/setup-buildx-action@v3
21+ - uses : actions/setup-go@v5
22+ with :
23+ go-version-file : go.mod
24+ - name : Download integrations
25+ run : go run downloader.go
26+
27+ - name : Build and load docker image
28+ run : |
29+ DOCKER_PLATFORMS=linux/amd64 ./docker-build.sh . --load
30+
31+ # ... (previous steps: checkout, setup-go, build docker image) ...
32+
33+ - name : Create Scan Template
34+ run : |
35+ cat << 'EOF' > scan.tpl
36+ {{- range . -}}
37+ {{- $target := .Target -}}
38+ {{- range .Vulnerabilities -}}
39+ {{ $target }} | {{ .PkgName }} | {{ .VulnerabilityID }} | {{ .Severity }} | {{ .FixedVersion }}
40+ {{ end -}}
41+ {{- end -}}
42+ EOF
43+
44+ - name : Run Trivy Scan
45+ uses : aquasecurity/trivy-action@0.29.0
46+ with :
47+ image-ref : ${{ env.DOCKER_IMAGE }}:${{ env.DOCKER_IMAGE_TAG }}
48+ format : ' template'
49+ template : ' @scan.tpl'
50+ ignore-unfixed : true
51+ severity : " CRITICAL,HIGH,MEDIUM,LOW"
52+ output : " trivy-report.txt"
53+
54+ - name : Run Grype Scan
55+ uses : anchore/scan-action@v6
56+ id : grype
57+ with :
58+ image : " ${{ env.DOCKER_IMAGE }}:${{ env.DOCKER_IMAGE_TAG }}"
59+ fail-build : false
60+ severity-cutoff : low
61+ only-fixed : true
62+ output-format : json # Use JSON for raw data
63+
64+ - name : Convert Grype to Table Format & Merge
65+ run : |
66+ # 1. Use jq to convert Grype JSON to the same format: Target | Pkg | CVE | Severity | Fixed
67+ # We use "any" for Fixed Version if not explicitly found to keep column count consistent
68+ jq -r '.matches[] | "\(.artifact.locations[0].path) | \(.artifact.name) | \(.vulnerability.id) | \(.vulnerability.severity | ascii_upcase) | \(.vulnerability.fix.versions[0] // "n/a")"' ${{ steps.grype.outputs.json }} > grype-report.txt || touch grype-report.txt
69+
70+ # 2. Merge both reports
71+ cat trivy-report.txt grype-report.txt > combined-raw.txt
72+
73+ # 3. Clean up the paths and de-duplicate unique CVEs per integration
74+ # This ensures that if both find the same CVE in the same binary, we only count it once
75+ sed -i 's|var/db/newrelic-infra/newrelic-integrations/bin/||g' combined-raw.txt
76+ sort -u combined-raw.txt -o raw-report.txt
77+
78+ - name : Format Slack Message
79+ id : format_message
80+ run : |
81+ # 1. Clean the report
82+ sed -i '/^[[:space:]]*$/d' raw-report.txt
83+
84+ if [ -s raw-report.txt ]; then
85+ # --- LOGIC: GLOBAL COUNTS (Strictly Cleaned) ---
86+ # We use head -n 1 to ensure only one line is grabbed
87+ # We use sed to strip any non-numeric characters
88+ TOTAL_CVE_COUNT=$(grep -c "CVE-" raw-report.txt | head -n 1 | sed 's/[^0-9]//g')
89+ UNIQUE_INT_COUNT=$(cut -d'|' -f1 raw-report.txt | sort -u | wc -l | head -n 1 | sed 's/[^0-9]//g')
90+ CRIT_T=$(grep -c "CRITICAL" raw-report.txt | head -n 1 | sed 's/[^0-9]//g')
91+ HIGH_T=$(grep -c "HIGH" raw-report.txt | head -n 1 | sed 's/[^0-9]//g')
92+ MED_T=$(grep -c "MEDIUM" raw-report.txt | head -n 1 | sed 's/[^0-9]//g')
93+ LOW_T=$(grep -c "LOW" raw-report.txt | head -n 1 | sed 's/[^0-9]//g')
94+
95+ # Fallback to 0 if variables are empty
96+ TOTAL_CVE_COUNT=${TOTAL_CVE_COUNT:-0}
97+ UNIQUE_INT_COUNT=${UNIQUE_INT_COUNT:-0}
98+ CRIT_T=${CRIT_T:-0}
99+ HIGH_T=${HIGH_T:-0}
100+ MED_T=${MED_T:-0}
101+ LOW_T=${LOW_T:-0}
102+
103+ # --- LOGIC: PER-INTEGRATION AGGREGATION ---
104+ AGGREGATED_TABLE=$(awk -F'|' '
105+ {
106+ name = $1;
107+ gsub(/.*\//, "", name); gsub(/ /, "", name);
108+ sev = $4; gsub(/ /, "", sev);
109+
110+ ints[name]++;
111+ if (sev == "CRITICAL") crit[name]++;
112+ else if (sev == "HIGH") high[name]++;
113+ else if (sev == "MEDIUM") med[name]++;
114+ else if (sev == "LOW") low[name]++;
115+ }
116+ END {
117+ for (i in ints) {
118+ if (crit[i] > 0) icon = "🔴";
119+ else if (high[i] > 0) icon = "🟠";
120+ else if (med[i] > 0) icon = "🟡";
121+ else icon = "🔵";
122+ row_tot = (crit[i]+0) + (high[i]+0) + (med[i]+0) + (low[i]+0);
123+ printf "%s %-12s | %2d | %2d | %2d | %2d | %3d\n", icon, substr(i,1,12), crit[i], high[i], med[i], low[i], row_tot
124+ }
125+ }' raw-report.txt | sort -r)
126+
127+ # --- CONSTRUCT DASHBOARD ---
128+ {
129+ echo "SLACK_MESSAGE<<EOF"
130+ echo -e "*🛡️ SECURITY SCAN REPORT* | \`$(date +'%Y-%m-%d')\`"
131+ echo -e "*Multi-Engine:* _Trivy & Grype Combined_"
132+ echo -e "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
133+ echo -e "*📊 OVERALL POSTURE*"
134+ echo -e "• *Total Issues:* \`$TOTAL_CVE_COUNT\` findings across \`$UNIQUE_INT_COUNT\` components"
135+ echo -e "• *Critical:* 🔴 \`$CRIT_T\` | *High:* 🟠 \`$HIGH_T\`"
136+ echo -e "• *Medium:* 🟡 \`$MED_T\` | *Low:* 🔵 \`$LOW_T\`"
137+ echo -e "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
138+ echo -e "*🔎 FINDINGS BREAKDOWN:*"
139+ echo -e "\`\`\`"
140+ echo -e "ST INTEGRATION | CR | HI | ME | LO | TOT"
141+ echo -e "--- ------------ | -- | -- | -- | -- | ---"
142+ echo -e "$AGGREGATED_TABLE"
143+ echo -e "\`\`\`"
144+ echo -e "🔗 _Check GitHub Actions for more details._"
145+ echo "EOF"
146+ } >> $GITHUB_OUTPUT
147+ else
148+ echo "SLACK_MESSAGE=*✅ SECURITY SCAN PASSED* - No issues detected." >> $GITHUB_OUTPUT
149+ fi
150+
151+ - name : Send Slack notification
152+ uses : archive/github-actions-slack@master
153+ with :
154+ slack-bot-user-oauth-access-token : ${{ secrets.COREINT_SLACK_TOKEN }}
155+ slack-channel : ${{ secrets.COREINT_SLACK_CHANNEL }}
156+ slack-text : ${{ steps.format_message.outputs.SLACK_MESSAGE }}
157+ slack-optional-parse : full
0 commit comments