@@ -20,41 +20,14 @@ jobs:
2020 creds : ' {"clientId":"${{ secrets.EPPLUS_CODE_SIGNING_APPLICATION_ID }}","clientSecret":"${{ secrets.EPPLUS_CODE_SIGNING_SECRET }}","subscriptionId":"${{ secrets.EPPLUS_CODE_SIGNING_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.EPPLUS_CODE_SIGNING_TENENT_ID }}"}'
2121
2222 - name : Install grype
23- run : curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin v0.110.0
24-
25- - name : Install syft
26- run : curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin v1.21.0
23+ run : curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
2724
2825 - name : Fetch SBOM index
2926 run : |
3027 curl -sSf https://epplussoftware.com/security/sbom/all.json -o all.json
3128 echo "SBOM index fetched:"
3229 cat all.json
3330
34- - name : Start scan
35- shell : bash
36- run : |
37- SCAN_ID=$(uuidgen)
38- echo "SCAN_ID=$SCAN_ID" >> $GITHUB_ENV
39-
40- GRYPE_VERSION=$(grype version | grep '^Version' | awk '{print $2}')
41-
42- response=$(curl -s -o response.json -w "%{http_code}" \
43- -X POST "https://epplussoftware.com/api/security/vulnerability/scan/start?scanId=${SCAN_ID}&grypeVersion=${GRYPE_VERSION}" \
44- -H "X-Api-Key: ${{ secrets.EPPLUS_VULNERABILITY_API_KEY }}")
45-
46- if [ "$response" != "200" ]; then
47- echo "ERROR: Failed to start scan with HTTP $response"
48- cat response.json
49- exit 1
50- fi
51-
52- SCAN_DB_ID=$(jq -r '.scanDbId' response.json)
53- echo "SCAN_DB_ID=$SCAN_DB_ID" >> $GITHUB_ENV
54-
55- echo "Scan started with ID $SCAN_ID (db id: $SCAN_DB_ID)"
56- cat response.json
57-
5831 - name : Scan each SBOM
5932 shell : bash
6033 run : |
@@ -64,53 +37,83 @@ jobs:
6437 for row in $versions; do
6538 entry=$(echo "$row" | base64 --decode)
6639 version=$(echo "$entry" | jq -r '.version')
67- echo "--- Processing EPPlus $version ---"
68-
69- # Scan per-TFM SBOMs
70- tfms=$(echo "$entry" | jq -r '.targetFrameworks[]? | @base64')
71- for tfm_row in $tfms; do
72- tfm_entry=$(echo "$tfm_row" | base64 --decode)
73- tfm=$(echo "$tfm_entry" | jq -r '.tfm')
74- expected_sha256=$(echo "$tfm_entry" | jq -r '.sha256' | tr -d '\xEF\xBB\xBF' | tr -d '[:space:]')
75-
76- echo " -> TFM: $tfm"
77-
78- # Download per-TFM SBOM
79- sbom_file="epplus-${version}.${tfm}.sbom.json"
80- curl -sSf "https://epplussoftware.com/security/sbom/${version}/${tfm}.json" -o "$sbom_file"
81-
82- # Validate checksum
83- actual_sha256=$(sha256sum "$sbom_file" | awk '{ print $1 }')
84- if [ "$actual_sha256" != "$expected_sha256" ]; then
85- echo "ERROR: Checksum mismatch for EPPlus $version / $tfm"
86- echo " Expected: $expected_sha256"
87- echo " Actual: $actual_sha256"
88- exit 1
89- fi
90- echo " Checksum OK for $version / $tfm"
40+ expected_sha256=$(echo "$entry" | jq -r '.sha256' | tr -d '\xEF\xBB\xBF' | tr -d '[:space:]')
9141
92- # Convert CycloneDX SBOM to Syft JSON format
93- syft_file="epplus-${version}.${tfm}.syft.json"
94- syft scan "file:./${sbom_file}" -o "syft-json=./${syft_file}"
42+ echo "--- Processing EPPlus $version ---"
9543
96- # Run grype scan
97- mkdir -p "./reports/${version}/${tfm}"
98- grype --add-cpes-if-none "sbom:./${syft_file}" --output json --file "./reports/${version}/${tfm}/report.json"
99- echo " Scan complete for $version / $tfm"
100- done
44+ # Download combined SBOM directly from Azure Blob Storage to avoid web cache
45+ sbom_file="epplus-${version}.sbom.json"
46+ az storage blob download \
47+ --account-name eppluswebprod \
48+ --container-name sbom \
49+ --name "$sbom_file" \
50+ --file "$sbom_file" \
51+ --auth-mode login
52+
53+ # Validate checksum
54+ actual_sha256=$(sha256sum "$sbom_file" | awk '{ print $1 }')
55+ if [ "$actual_sha256" != "$expected_sha256" ]; then
56+ echo "ERROR: Checksum mismatch for EPPlus $version"
57+ echo " Expected: $expected_sha256"
58+ echo " Actual: $actual_sha256"
59+ exit 1
60+ fi
61+ echo "Checksum OK for EPPlus $version"
62+
63+ # Scan per-TFM SBOMs if available, otherwise fall back to combined SBOM
64+ tfm_entries=$(echo "$entry" | jq -r '.targetFrameworks // [] | .[] | @base64')
65+ if [ -n "$tfm_entries" ]; then
66+ for tfm_row in $tfm_entries; do
67+ tfm_entry=$(echo "$tfm_row" | base64 --decode)
68+ tfm=$(echo "$tfm_entry" | jq -r '.tfm')
69+ expected_tfm_sha256=$(echo "$tfm_entry" | jq -r '.sha256' | tr -d '\xEF\xBB\xBF' | tr -d '[:space:]')
70+
71+ echo " Scanning TFM: $tfm"
72+
73+ # Download per-TFM SBOM directly from Azure Blob Storage
74+ tfm_sbom_file="epplus-${version}.${tfm}.sbom.json"
75+ az storage blob download \
76+ --account-name eppluswebprod \
77+ --container-name sbom \
78+ --name "$tfm_sbom_file" \
79+ --file "$tfm_sbom_file" \
80+ --auth-mode login
81+
82+ # Validate checksum
83+ actual_tfm_sha256=$(sha256sum "$tfm_sbom_file" | awk '{ print $1 }')
84+ if [ "$actual_tfm_sha256" != "$expected_tfm_sha256" ]; then
85+ echo "ERROR: Checksum mismatch for EPPlus $version / $tfm"
86+ echo " Expected: $expected_tfm_sha256"
87+ echo " Actual: $actual_tfm_sha256"
88+ exit 1
89+ fi
90+ echo " Checksum OK for EPPlus $version / $tfm"
91+
92+ # Run grype directly against CycloneDX SBOM
93+ mkdir -p "./reports/${version}/${tfm}"
94+ grype --add-cpes-if-none "sbom:./${tfm_sbom_file}" --output json --file "./reports/${version}/${tfm}/report.json"
95+ echo " Scan complete for EPPlus $version / $tfm"
96+ done
97+ else
98+ # No per-TFM SBOMs — scan combined SBOM
99+ echo " No per-TFM SBOMs found, scanning combined SBOM"
100+ mkdir -p "./reports/${version}"
101+ grype --add-cpes-if-none "sbom:./${sbom_file}" --output json --file "./reports/${version}/report.json"
102+ echo " Scan complete for EPPlus $version (combined)"
103+ fi
101104 done
102105
103106 - name : Upload reports to Azure Blob Storage
104107 shell : bash
105108 run : |
106- for report in ./reports/*/*/ report.json; do
107- version=$(echo "$report" | sed 's| ./reports/||' | cut -d'/' -f1)
108- tfm=$(echo "$ report" | sed 's| ./reports/||' | cut -d'/' -f2)
109- echo "Uploading report for EPPlus $version / $tfm "
109+ find ./reports -name " report.json" | while read report ; do
110+ # Strip leading ./reports/ to get the blob name
111+ blob_name="${ report# ./reports/}"
112+ echo "Uploading $blob_name "
110113 az storage blob upload \
111114 --account-name eppluswebprod \
112115 --container-name vulnerability-reports \
113- --name "${version}/${tfm}/report.json " \
116+ --name "$blob_name " \
114117 --file "$report" \
115118 --auth-mode login \
116119 --overwrite
@@ -124,61 +127,43 @@ jobs:
124127 entry=$(echo "$row" | base64 --decode)
125128 version=$(echo "$entry" | jq -r '.version')
126129
127- tfms=$(echo "$entry" | jq -r '.targetFrameworks[]? | @base64')
128- for tfm_row in $tfms; do
129- tfm_entry=$(echo "$tfm_row" | base64 --decode)
130- tfm=$(echo "$tfm_entry" | jq -r '.tfm')
131-
132- echo "--- Indexing EPPlus $version / $tfm ---"
130+ tfm_entries=$(echo "$entry" | jq -r '.targetFrameworks // [] | .[] | @base64')
131+ if [ -n "$tfm_entries" ]; then
132+ for tfm_row in $tfm_entries; do
133+ tfm_entry=$(echo "$tfm_row" | base64 --decode)
134+ tfm=$(echo "$tfm_entry" | jq -r '.tfm')
135+
136+ echo "--- Indexing EPPlus $version / $tfm ---"
137+ response=$(curl -s -o response.json -w "%{http_code}" \
138+ -X POST "https://epplussoftware.com/api/security/vulnerability/index/${version}?tfm=${tfm}" \
139+ -H "X-Api-Key: ${{ secrets.EPPLUS_VULNERABILITY_API_KEY }}" \
140+ -H "Content-Type: application/json" \
141+ -d @"./reports/${version}/${tfm}/report.json")
142+
143+ if [ "$response" != "200" ]; then
144+ echo "ERROR: Indexing failed for EPPlus $version / $tfm with HTTP $response"
145+ cat response.json
146+ exit 1
147+ fi
148+
149+ echo "Indexed EPPlus $version / $tfm successfully"
150+ cat response.json
151+ done
152+ else
153+ echo "--- Indexing EPPlus $version (combined) ---"
133154 response=$(curl -s -o response.json -w "%{http_code}" \
134- -X POST "https://epplussoftware.com/api/security/vulnerability/index/${version}?tfm=${tfm}&scanId=${SCAN_DB_ID} " \
155+ -X POST "https://epplussoftware.com/api/security/vulnerability/index/${version}" \
135156 -H "X-Api-Key: ${{ secrets.EPPLUS_VULNERABILITY_API_KEY }}" \
136157 -H "Content-Type: application/json" \
137- -d @"./reports/${version}/${tfm}/ report.json")
158+ -d @"./reports/${version}/report.json")
138159
139160 if [ "$response" != "200" ]; then
140- echo "ERROR: Indexing failed for EPPlus $version / $tfm with HTTP $response"
161+ echo "ERROR: Indexing failed for EPPlus $version with HTTP $response"
141162 cat response.json
142163 exit 1
143164 fi
144165
145- echo "Indexed EPPlus $version / $tfm successfully"
166+ echo "Indexed EPPlus $version successfully"
146167 cat response.json
147- done
148- done
149-
150- - name : Complete scan
151- shell : bash
152- if : success()
153- run : |
154- response=$(curl -s -o response.json -w "%{http_code}" \
155- -X POST "https://epplussoftware.com/api/security/vulnerability/scan/complete?scanId=${SCAN_ID}&status=completed" \
156- -H "X-Api-Key: ${{ secrets.EPPLUS_VULNERABILITY_API_KEY }}")
157-
158- if [ "$response" != "200" ]; then
159- echo "ERROR: Failed to complete scan with HTTP $response"
160- cat response.json
161- exit 1
162- fi
163-
164- echo "Scan $SCAN_ID completed successfully"
165- cat response.json
166-
167- - name : Fail scan
168- shell : bash
169- if : failure()
170- run : |
171- if [ -n "$SCAN_ID" ]; then
172- response=$(curl -s -o response.json -w "%{http_code}" \
173- -X POST "https://epplussoftware.com/api/security/vulnerability/scan/complete?scanId=${SCAN_ID}&status=failed" \
174- -H "X-Api-Key: ${{ secrets.EPPLUS_VULNERABILITY_API_KEY }}")
175-
176- if [ "$response" != "200" ]; then
177- echo "WARNING: Failed to mark scan as failed with HTTP $response"
178- cat response.json
179- else
180- echo "Scan $SCAN_ID marked as failed"
181168 fi
182- else
183- echo "No SCAN_ID available, skipping fail notification"
184- fi
169+ done
0 commit comments