Skip to content

Commit 9363569

Browse files
committed
Update release
1 parent d63f187 commit 9363569

4 files changed

Lines changed: 418 additions & 81 deletions

File tree

.github/workflows/release.yml

Lines changed: 149 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -75,15 +75,130 @@ jobs:
7575
path: ${{ matrix.asset_name }}
7676
if-no-files-found: error
7777

78-
release:
79-
name: Create GitHub Release
78+
# Collect all matrix binaries and compute hashes for SLSA provenance.
79+
hashes:
8080
needs: build
8181
runs-on: ubuntu-latest
82+
permissions: {}
83+
outputs:
84+
hashes: ${{ steps.hash.outputs.hashes }}
85+
steps:
86+
- uses: actions/download-artifact@v4
87+
with:
88+
path: artifacts
89+
merge-multiple: true
90+
91+
- name: Compute SHA-256 hashes
92+
id: hash
93+
run: |
94+
echo "hashes=$(sha256sum artifacts/* | base64 -w0)" >> "$GITHUB_OUTPUT"
95+
96+
# SLSA Level 3 provenance covers all release binaries.
97+
provenance:
98+
needs: hashes
8299
permissions:
100+
actions: read
101+
id-token: write
83102
contents: write
103+
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
104+
with:
105+
base64-subjects: ${{ needs.hashes.outputs.hashes }}
106+
upload-assets: false
107+
108+
# Scans all four binaries concurrently to minimise wall-clock time.
109+
virustotal:
110+
needs: build
111+
runs-on: ubuntu-latest
112+
permissions: {}
84113
outputs:
85-
version: ${{ steps.meta.outputs.version }}
86-
release_url: ${{ steps.publish.outputs.url }}
114+
arm64_report: ${{ steps.scan.outputs.arm64_report }}
115+
arm64_status: ${{ steps.scan.outputs.arm64_status }}
116+
x86_64_mac_report: ${{ steps.scan.outputs.x86_64_mac_report }}
117+
x86_64_mac_status: ${{ steps.scan.outputs.x86_64_mac_status }}
118+
linux_report: ${{ steps.scan.outputs.linux_report }}
119+
linux_status: ${{ steps.scan.outputs.linux_status }}
120+
windows_report: ${{ steps.scan.outputs.windows_report }}
121+
windows_status: ${{ steps.scan.outputs.windows_status }}
122+
steps:
123+
- uses: actions/download-artifact@v4
124+
with:
125+
path: artifacts
126+
merge-multiple: true
127+
128+
- name: Scan with VirusTotal
129+
id: scan
130+
env:
131+
VT_API_KEY: ${{ secrets.VIRUSTOTAL_API_KEY }}
132+
run: |
133+
upload_and_poll() {
134+
local file="$1" outprefix="$2"
135+
local sha256 size_bytes upload_url analysis_id last_response poll_status
136+
local malicious suspicious undetected harmless total detected result_status
137+
sha256=$(sha256sum "$file" | awk '{print $1}')
138+
size_bytes=$(stat -c%s "$file")
139+
upload_url="https://www.virustotal.com/api/v3/files"
140+
if [ "$size_bytes" -gt 33554432 ]; then
141+
upload_url=$(curl -sS --request GET \
142+
--url https://www.virustotal.com/api/v3/files/upload_url \
143+
--header "x-apikey: $VT_API_KEY" | jq -r '.data')
144+
if [ -z "$upload_url" ] || [ "$upload_url" = "null" ]; then
145+
printf '%s' "https://www.virustotal.com/gui/file/$sha256/detection" > "${outprefix}.url"
146+
printf '%s' "⬜ N/A" > "${outprefix}.status"
147+
return
148+
fi
149+
fi
150+
analysis_id=$(curl -sS --request POST \
151+
--url "$upload_url" \
152+
--header "x-apikey: $VT_API_KEY" \
153+
--form "file=@$file" | jq -r '.data.id')
154+
if [ -z "$analysis_id" ] || [ "$analysis_id" = "null" ]; then
155+
printf '%s' "https://www.virustotal.com/gui/file/$sha256/detection" > "${outprefix}.url"
156+
printf '%s' "⬜ N/A" > "${outprefix}.status"
157+
return
158+
fi
159+
last_response=""
160+
for i in $(seq 1 30); do
161+
sleep 20
162+
last_response=$(curl -sS \
163+
--url "https://www.virustotal.com/api/v3/analyses/$analysis_id" \
164+
--header "x-apikey: $VT_API_KEY")
165+
poll_status=$(echo "$last_response" | jq -r '.data.attributes.status')
166+
[ "$poll_status" = "completed" ] && break
167+
done
168+
malicious=$(echo "$last_response" | jq -r '.data.attributes.stats.malicious // 0')
169+
suspicious=$(echo "$last_response" | jq -r '.data.attributes.stats.suspicious // 0')
170+
undetected=$(echo "$last_response" | jq -r '.data.attributes.stats.undetected // 0')
171+
harmless=$(echo "$last_response" | jq -r '.data.attributes.stats.harmless // 0')
172+
total=$((malicious + suspicious + undetected + harmless))
173+
detected=$((malicious + suspicious))
174+
[ "$detected" -eq 0 ] \
175+
&& result_status="✅ ${detected}/${total} Clean" \
176+
|| result_status="⚠️ ${detected}/${total} Detected"
177+
printf '%s' "https://www.virustotal.com/gui/file/$sha256/detection" > "${outprefix}.url"
178+
printf '%s' "$result_status" > "${outprefix}.status"
179+
}
180+
upload_and_poll "artifacts/plainapp-cli-macos-arm64" /tmp/vt_arm64 &
181+
upload_and_poll "artifacts/plainapp-cli-macos-x86_64" /tmp/vt_x86_64_mac &
182+
upload_and_poll "artifacts/plainapp-cli-linux-x86_64" /tmp/vt_linux &
183+
upload_and_poll "artifacts/plainapp-cli-windows-x86_64.exe" /tmp/vt_windows &
184+
wait
185+
{
186+
echo "arm64_report=$(cat /tmp/vt_arm64.url)"
187+
echo "arm64_status=$(cat /tmp/vt_arm64.status)"
188+
echo "x86_64_mac_report=$(cat /tmp/vt_x86_64_mac.url)"
189+
echo "x86_64_mac_status=$(cat /tmp/vt_x86_64_mac.status)"
190+
echo "linux_report=$(cat /tmp/vt_linux.url)"
191+
echo "linux_status=$(cat /tmp/vt_linux.status)"
192+
echo "windows_report=$(cat /tmp/vt_windows.url)"
193+
echo "windows_status=$(cat /tmp/vt_windows.status)"
194+
} >> $GITHUB_OUTPUT
195+
196+
release:
197+
name: Create GitHub Release
198+
needs: [provenance, virustotal]
199+
runs-on: ubuntu-latest
200+
permissions:
201+
contents: write
87202
steps:
88203
- uses: actions/checkout@v4
89204

@@ -93,22 +208,24 @@ jobs:
93208
VER=$(grep '^version' Cargo.toml | head -1 | awk -F '"' '{print $2}')
94209
echo "version=$VER" >> "$GITHUB_OUTPUT"
95210
96-
- name: Download all artifacts
97-
uses: actions/download-artifact@v4
211+
- uses: actions/download-artifact@v4
98212
with:
99213
path: artifacts
214+
merge-multiple: true
215+
216+
- uses: actions/download-artifact@v4
217+
with:
218+
name: ${{ needs.provenance.outputs.provenance-name }}
100219

101220
- name: Compute SHA-256 checksums
102221
run: |
103-
cd artifacts
104-
find . -type f | sort | xargs sha256sum > ../checksums.txt
105-
cat ../checksums.txt
222+
sha256sum artifacts/* ${{ needs.provenance.outputs.provenance-name }} > checksums.txt
223+
cat checksums.txt
106224
107225
- name: Publish GitHub Release (draft)
108-
id: publish
109-
uses: softprops/action-gh-release@v2
226+
uses: ncipollo/release-action@v1
110227
with:
111-
tag_name: v${{ steps.meta.outputs.version }}
228+
tag: v${{ steps.meta.outputs.version }}
112229
name: plainapp-cli v${{ steps.meta.outputs.version }}
113230
body: |
114231
## Install
@@ -124,10 +241,27 @@ jobs:
124241
sudo mv plainapp-cli-* /usr/local/bin/plainapp-cli
125242
```
126243
244+
## Security
245+
246+
### VirusTotal Scan
247+
| File | Status | Scan Report |
248+
|------|--------|-------------|
249+
| `plainapp-cli-macos-arm64` | ${{ needs.virustotal.outputs.arm64_status }} | [View Report](${{ needs.virustotal.outputs.arm64_report }}) |
250+
| `plainapp-cli-macos-x86_64` | ${{ needs.virustotal.outputs.x86_64_mac_status }} | [View Report](${{ needs.virustotal.outputs.x86_64_mac_report }}) |
251+
| `plainapp-cli-linux-x86_64` | ${{ needs.virustotal.outputs.linux_status }} | [View Report](${{ needs.virustotal.outputs.linux_report }}) |
252+
| `plainapp-cli-windows-x86_64.exe` | ${{ needs.virustotal.outputs.windows_status }} | [View Report](${{ needs.virustotal.outputs.windows_report }}) |
253+
254+
### SLSA Provenance (Level 3)
255+
The `.intoto.jsonl` file is a signed SLSA provenance document covering all release binaries.
256+
Verify with [slsa-verifier](https://github.com/slsa-framework/slsa-verifier):
257+
```sh
258+
slsa-verifier verify-artifact plainapp-cli-linux-x86_64 \
259+
--provenance-path ${{ needs.provenance.outputs.provenance-name }} \
260+
--source-uri github.com/${{ github.repository }}
261+
```
262+
127263
## Checksums (SHA-256)
128264
See `checksums.txt` attached below.
129-
files: |
130-
artifacts/**/*
131-
checksums.txt
132265
draft: true
133266
prerelease: false
267+
artifacts: "artifacts/*,${{ needs.provenance.outputs.provenance-name }},checksums.txt"

0 commit comments

Comments
 (0)