-
Notifications
You must be signed in to change notification settings - Fork 137
164 lines (152 loc) · 6.72 KB
/
Copy pathrelease-scan.yml
File metadata and controls
164 lines (152 loc) · 6.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
---
name: 'Release vulnerability scan'
# Scans the binaries Kairos actually ships, for the kairos-init version this
# repo pins. Refs https://github.com/kairos-io/kairos/issues/3985.
#
# Why this exists as a separate job rather than relying on the scanning already
# in release.yaml: kairos-init UPX-compresses the bundled binaries, and a
# UPX-packed Go binary no longer exposes its module metadata, so the
# dependencies we ship are invisible to a scanner. This job rebuilds the bundle
# with SKIP_UPX=true purely so it can be read.
#
# The scanned artifact is therefore NOT byte-identical to the shipped one. It is
# composed from the same sources at the same pins, which is the property that
# matters for dependency scanning.
#
# Triggers are deliberately narrow. This is a release gate: release.yaml and
# release-arm.yaml call it via workflow_call, and every publishing job depends
# on it, so a release with unignored advisories never publishes. It does NOT run on every pull request: the
# findings belong to the pinned dependency set, not to the change under review,
# so an unrelated PR -- including an external contributor's -- would get a red
# check for CVEs it did not introduce and cannot fix.
#
# The pull_request trigger is scoped to the files that actually determine the
# result: the kairos-init pin, the ignore list, and this workflow itself.
on:
workflow_call:
workflow_dispatch:
pull_request:
paths:
- 'images/Dockerfile'
- 'osv-scanner.toml'
- '.github/workflows/release-scan.yml'
permissions:
contents: read
jobs:
scan-shipped-bundle:
name: scan shipped bundle
runs-on: ubuntu-latest
steps:
- name: Checkout kairos
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Resolve the pinned kairos-init version
id: pin
run: |
set -euo pipefail
VERSION="$(grep -m1 '^ARG KAIROS_INIT=' images/Dockerfile | cut -d= -f2)"
if [ -z "${VERSION}" ]; then
echo "::error::could not read ARG KAIROS_INIT from images/Dockerfile"
exit 1
fi
echo "version=${VERSION}" >> "${GITHUB_OUTPUT}"
echo "Scanning the bundle composed by kairos-init ${VERSION}"
- name: Checkout kairos-init at that version
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
repository: kairos-io/kairos-init
ref: ${{ steps.pin.outputs.version }}
path: kairos-init
# SKIP_UPX is the whole point of this job -- see the header comment.
- name: Download the shipped binaries, uncompressed
working-directory: kairos-init
run: SKIP_UPX=true make download
- name: Install osv-scanner
env:
OSV_SCANNER_VERSION: v2.5.0
run: |
set -euo pipefail
curl -sSfL --retry 5 --retry-all-errors --retry-delay 2 \
"https://github.com/google/osv-scanner/releases/download/${OSV_SCANNER_VERSION}/osv-scanner_linux_amd64" \
-o /usr/local/bin/osv-scanner
chmod +x /usr/local/bin/osv-scanner
osv-scanner --version
# Two flags are load-bearing. Without either, the scan finds nothing and
# reports success -- a false green, which is worse than no gate at all.
#
# --no-ignore kairos-init's .gitignore excludes
# pkg/bundled/binaries/, and osv-scanner
# honours .gitignore by default.
# --experimental-plugins artifact the default plugin set
# (lockfile, sbom, directory) cannot
# read Go binaries at all.
#
# continue-on-error because osv-scanner exits non-zero when it finds
# vulnerabilities; the reporting step below decides what that means.
- name: Scan
continue-on-error: true
run: |
set -uo pipefail
osv-scanner scan source \
--experimental-plugins artifact \
--no-ignore \
--recursive \
--config osv-scanner.toml \
--format json \
--output-file osv.json \
kairos-init/pkg/bundled/binaries
# A clean result only means something if the scanner actually read the
# binaries. Zero extracted packages is a broken scan, not a pass.
- name: Guard against a false green
run: |
set -euo pipefail
if [ ! -s osv.json ]; then
echo "::error::osv-scanner produced no output file"
exit 1
fi
PACKAGES="$(jq '[.results[].packages[]] | length' osv.json)"
echo "extracted packages: ${PACKAGES}"
if [ "${PACKAGES}" -eq 0 ]; then
echo "::error::scanner extracted 0 packages -- it did not read the shipped binaries. Treating as a failure, not as clean."
exit 1
fi
- name: Report and enforce
run: |
set -euo pipefail
# One advisory can affect several module versions in the same bundle
# (different binaries pin different versions), so a finding is an
# (advisory, module@version) pair and the two counts differ. Report
# both rather than calling every row an advisory.
jq -r '
[ .results[].packages[]
| . as $pkg
| .vulnerabilities[]?
| "| `\(.id)` | `\($pkg.package.name)@\($pkg.package.version)` |"
] | unique | .[]' osv.json > findings.md || true
FINDINGS="$(wc -l < findings.md)"
ADVISORIES="$(jq -r '[.results[].packages[].vulnerabilities[]?.id] | unique | length' osv.json)"
{
echo "## Vulnerability scan of the shipped bundle"
echo
echo "kairos-init \`${{ steps.pin.outputs.version }}\`, scanned uncompressed."
echo
if [ "${FINDINGS}" -eq 0 ]; then
echo "No unignored advisories."
else
echo "**${ADVISORIES} advisories**, ${FINDINGS} findings across module versions."
echo
echo "| advisory | module |"
echo "|---|---|"
cat findings.md
fi
} >> "${GITHUB_STEP_SUMMARY}"
if [ "${FINDINGS}" -gt 0 ]; then
echo "::error::${ADVISORIES} unignored advisories (${FINDINGS} findings) in the shipped bundle. Bump the affected component, or add a dated entry to osv-scanner.toml with a reason."
exit 1
fi
- name: Upload scan output
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: osv-scan
path: osv.json
if-no-files-found: warn