Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/actions/locked-tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ Install checksum-verified release tools before configuring release credentials:
The action owns the version/URL/checksum reader. It resolves its canonical
`tool-locks.txt` from the action directory, not from a caller checkout, verifies
artifacts before extraction, and checks each expected executable leaf before
publishing PATH. Requesting `gon` also installs Go first.
publishing PATH. Requesting `gon` or `govulncheck` also installs Go first, then
builds the locked source commit with readonly, checksum-authenticated modules.

## Inputs and outputs

Expand All @@ -26,7 +27,7 @@ Outputs:

| Output | Contract |
| --- | --- |
| `go`, `node`, `syft`, `goreleaser`, `goreleaser-pro`, `gon`, `minisign` | Installed tool version, or source commit for gon; empty when not requested. Go includes its `go` prefix. |
| `go`, `node`, `syft`, `goreleaser`, `goreleaser-pro`, `gon`, `govulncheck`, `minisign` | Installed tool version, or source commit for gon/govulncheck; empty when not requested. Go includes its `go` prefix. |
| `versions` | JSON per-tool map of the installed versions/source commits. |
| `lock_sha256` | SHA256 of the exact reviewed lock used throughout preparation, installation and output publication. |
| `lock_path` | Absolute path of that same existing lock file. Action-local by default; the resolved reviewed override when explicitly supplied. Native Windows form on Windows. |
Expand Down Expand Up @@ -55,7 +56,7 @@ or any other checkout copy:
```

The same environment applies to `verify-locked-go.sh version`. Request `go`
(or `gon`, which adds it) to obtain a nonempty expected Go version.
(or `gon`/`govulncheck`, which add it) to obtain a nonempty expected Go version.

All three expectation variables are required. The gate validates their shape,
requires an absolute readable `LOCK_PATH`, and hashes its bytes to assert the
Expand Down
33 changes: 31 additions & 2 deletions .github/actions/locked-tools/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Locked release tools
description: Install checksum-verified release tools before configuring release credentials.
inputs:
tools:
description: Comma-separated tools (go,node,syft,goreleaser,goreleaser-pro,gon,minisign). Gon automatically adds Go.
description: Comma-separated tools (go,node,syft,goreleaser,goreleaser-pro,gon,govulncheck,minisign). Gon and govulncheck automatically add Go.
required: true
locks:
description: Path to the reviewed tool lock file; empty selects the action-local canonical lock.
Expand All @@ -12,7 +12,7 @@ inputs:
default: ${{ runner.temp }}/locked-tools
outputs:
go:
description: Installed Go version, when requested or required by gon.
description: Installed Go version, when requested or required by a source-built tool.
value: ${{ steps.result.outputs.go }}
node:
description: Installed Node version, when requested.
Expand All @@ -29,6 +29,9 @@ outputs:
gon:
description: Built gon source commit, when requested.
value: ${{ steps.result.outputs.gon }}
govulncheck:
description: Built govulncheck source commit, when requested.
value: ${{ steps.result.outputs.govulncheck }}
minisign:
description: Installed Minisign version, when requested.
value: ${{ steps.result.outputs.minisign }}
Expand Down Expand Up @@ -63,6 +66,15 @@ runs:
path: ${{ steps.prepare.outputs.gon_path }}
persist-credentials: false

- name: Checkout locked govulncheck source
if: steps.prepare.outputs.govulncheck_ref != ''
uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # actions/checkout@v5
with:
repository: golang/vuln
ref: ${{ steps.prepare.outputs.govulncheck_ref }}
path: ${{ steps.prepare.outputs.govulncheck_path }}
persist-credentials: false

- name: Install verified artifacts
shell: bash
env:
Expand Down Expand Up @@ -91,6 +103,23 @@ runs:
GOSUMDB: sum.golang.org
run: bash "$GITHUB_ACTION_PATH/locked-tools.sh" gon

- name: Build locked govulncheck source
if: steps.prepare.outputs.govulncheck_ref != ''
shell: bash
env:
LOCKED_TOOLS: ${{ steps.prepare.outputs.tools }}
TOOL_LOCK_FILE: ${{ steps.prepare.outputs.lock_file }}
LOCK_PATH: ${{ steps.prepare.outputs.lock_path }}
EXPECTED_GO_VERSION: ${{ steps.prepare.outputs.go_version }}
LOCKED_TOOLS_DEST: ${{ steps.prepare.outputs.dest }}
EXPECTED_LOCK_SHA256: ${{ steps.prepare.outputs.lock_sha256 }}
LOCKED_GOVULNCHECK_PATH: ${{ steps.prepare.outputs.govulncheck_path }}
GOTOOLCHAIN: local
GOFLAGS: -mod=readonly
GOPROXY: https://proxy.golang.org
GOSUMDB: sum.golang.org
run: bash "$GITHUB_ACTION_PATH/locked-tools.sh" govulncheck

- name: Report installed tool versions and lock hash
id: result
shell: bash
Expand Down
49 changes: 49 additions & 0 deletions .github/actions/locked-tools/govulncheck-source.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# Build govulncheck from an immutable source commit with the locked Go toolchain.
set -euo pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
lock="${TOOL_LOCK_FILE:-$script_dir/tool-locks.txt}"
commit="$(awk '$1 == "govulncheck" && $3 == "linux/amd64" && $4 == "source" && NF == 4 { print $2; count++ } END { if (count != 1) exit 1 }' "$lock")"
if [[ ! "$commit" =~ ^[0-9a-f]{40}$ ]]; then
echo '::error title=locked-tools::Expected a single immutable govulncheck source commit lock' >&2
exit 1
fi
case "${1:-}" in
ref)
printf 'ref=%s\n' "$commit" >> "${GITHUB_OUTPUT:?GITHUB_OUTPUT must be set}"
;;
build)
if [ "$#" -ne 3 ]; then
echo 'Usage: govulncheck-source.sh build <source-dir> <dest-dir>' >&2
exit 1
fi
source_dir="$2"
dest="$3"
if [ "$(git -C "$source_dir" rev-parse HEAD)" != "$commit" ]; then
echo '::error title=locked-tools::govulncheck checkout does not match locked source commit' >&2
exit 1
fi
# Require the verified Go binary and checksum-authenticated module downloads.
if [ "${GOFLAGS:-}" != -mod=readonly ] || [ "${GOPROXY:-}" != https://proxy.golang.org ] || [ "${GOSUMDB:-}" != sum.golang.org ]; then
echo '::error title=locked-tools::govulncheck requires readonly modules, proxy.golang.org, and sum.golang.org' >&2
exit 1
fi
bash "$script_dir/verify-locked-go.sh" version
bash "$script_dir/verify-locked-go.sh" module "$source_dir/go.mod"
mkdir -p "$dest"
dest="$(cd "$dest" && pwd)"
(cd "$source_dir" && GOENV=off GOWORK=off GOPRIVATE='' GONOPROXY='' GONOSUMDB='' GOINSECURE='' \
go build -trimpath -o "$dest/govulncheck" ./cmd/govulncheck)
if [ ! -x "$dest/govulncheck" ]; then
echo '::error title=locked-tools::govulncheck source build did not produce an executable' >&2
exit 1
fi
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$dest/govulncheck"
else
shasum -a 256 "$dest/govulncheck"
fi
printf '%s\n' "$dest" >> "${GITHUB_PATH:?GITHUB_PATH must be set}"
;;
*) echo 'Usage: govulncheck-source.sh ref | build <source-dir> <dest-dir>' >&2; exit 1 ;;
esac
36 changes: 28 additions & 8 deletions .github/actions/locked-tools/locked-tools.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ set -euo pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
fail() { echo "::error title=locked-tools::$*" >&2; exit 1; }
phase="${1:-}"
case "$phase" in prepare|install|gon|outputs) ;; *) fail 'Unknown locked-tools phase' ;; esac
case "$phase" in prepare|install|gon|govulncheck|outputs) ;; *) fail 'Unknown locked-tools phase' ;; esac
request="${LOCKED_TOOLS:-}"
if [[ -z "$request" || "$request" == *$'\n'* || "$request" == *$'\r'* ]]; then
fail 'tools must be a nonempty comma-separated list'
Expand All @@ -20,15 +20,15 @@ for tool in "${requested[@]}"; do
tool="${tool#"${tool%%[![:space:]]*}"}"
tool="${tool%"${tool##*[![:space:]]}"}"
case "$tool" in
go|node|syft|goreleaser|goreleaser-pro|minisign|gon) ;;
go|node|syft|goreleaser|goreleaser-pro|minisign|gon|govulncheck) ;;
*) fail "Unsupported tool: $tool" ;;
esac
[[ "$seen" != *",$tool,"* ]] || fail "Duplicate tool: $tool"
seen+="$tool,"
tools+=("$tool")
if [ "$tool" = go ] || [ "$tool" = gon ]; then needs_go=true; fi
if [ "$tool" = go ] || [ "$tool" = gon ] || [ "$tool" = govulncheck ]; then needs_go=true; fi
done
# Go must precede every source build, even when gon is the only requested tool.
# Go must precede every source build, even when a source-built tool is the only request.
if [ "$needs_go" = true ]; then
ordered=(go)
for tool in "${tools[@]}"; do
Expand Down Expand Up @@ -74,13 +74,18 @@ lock_row() {
awk -v tool="$tool" -v platform="$platform" '$1 == tool && $3 == platform { print; count++ } END { if (count != 1) exit 1 }' "$lock" || fail "Expected exactly one $tool lock for $platform"
}
gon_ref=
govulncheck_ref=
for tool in "${tools[@]}"; do
row="$(lock_row "$tool")"
read -r _ version _ checksum url extra <<< "$row"
[[ "$version" =~ ^[a-zA-Z0-9][a-zA-Z0-9.+_-]*$ ]] || fail "Invalid locked version for $tool"
if [ "$tool" = gon ]; then
[[ "$version" =~ ^[0-9a-f]{40}$ && "$checksum" = source && -z "$url" && -z "$extra" ]] || fail 'Invalid gon source lock'
gon_ref="$version"
if [ "$tool" = gon ] || [ "$tool" = govulncheck ]; then
[[ "$version" =~ ^[0-9a-f]{40}$ && "$checksum" = source && -z "$url" && -z "$extra" ]] || fail "Invalid $tool source lock"
if [ "$tool" = gon ]; then
gon_ref="$version"
else
govulncheck_ref="$version"
fi
else
[[ "$checksum" =~ ^[0-9a-f]{64}$ && -n "$url" && -z "$extra" ]] || fail "Invalid artifact lock for $tool"
fi
Expand Down Expand Up @@ -119,15 +124,21 @@ case "$phase" in
source_dir="$(mktemp -d "${GITHUB_WORKSPACE:?GITHUB_WORKSPACE must be set}/.locked-tools-gon.XXXXXX")"
gon_path="$(basename "$source_dir")"
fi
govulncheck_path=
if [ -n "$govulncheck_ref" ]; then
source_dir="$(mktemp -d "${GITHUB_WORKSPACE:?GITHUB_WORKSPACE must be set}/.locked-tools-govulncheck.XXXXXX")"
govulncheck_path="$(basename "$source_dir")"
fi
{
printf 'tools=%s\nlock_file=%s\ndest=%s\nlock_sha256=%s\n' "$normalized" "$lock" "$dest" "$lock_sha256"
printf 'lock_path=%s\ngo_version=%s\n' "$lock_path" "$expected_go_version"
printf 'gon_ref=%s\ngon_path=%s\n' "$gon_ref" "$gon_path"
printf 'govulncheck_ref=%s\ngovulncheck_path=%s\n' "$govulncheck_ref" "$govulncheck_path"
} >> "${GITHUB_OUTPUT:?GITHUB_OUTPUT must be set}"
;;
install)
for tool in "${tools[@]}"; do
[ "$tool" != gon ] || continue
if [ "$tool" = gon ] || [ "$tool" = govulncheck ]; then continue; fi
target="$dest/$tool"
if [ "$tool" = go ]; then target="$dest"; fi
if [ "$os" = windows ]; then
Expand Down Expand Up @@ -157,6 +168,15 @@ case "$phase" in
# The action owns this unique checkout. Do not leave it in caller source trees.
rm -rf -- "$source_dir"
;;
govulncheck)
[ -n "$govulncheck_ref" ] || fail 'govulncheck was not requested'
govulncheck_path="${LOCKED_GOVULNCHECK_PATH:-}"
[[ "$govulncheck_path" =~ ^\.locked-tools-govulncheck\.[a-zA-Z0-9]+$ ]] || fail 'Invalid temporary govulncheck checkout path'
source_dir="${GITHUB_WORKSPACE:?GITHUB_WORKSPACE must be set}/$govulncheck_path"
bash "$script_dir/govulncheck-source.sh" build "$source_dir" "$dest/govulncheck"
# The action owns this unique checkout. Do not leave it in caller source trees.
rm -rf -- "$source_dir"
;;
outputs)
versions='{'
separator=
Expand Down
2 changes: 2 additions & 0 deletions .github/actions/locked-tools/tool-locks.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ goreleaser v2.13.3 darwin/arm64 5516c37779efb3935d5b213cda3b0b9025ae94ddbcb51df6
goreleaser-pro v2.13.3 windows/amd64 8f65fca431ae6aa083805c9287d4b98b7937d54649712f1185b0590ae02001de https://github.com/goreleaser/goreleaser-pro/releases/download/v2.13.3/goreleaser-pro_Windows_x86_64.zip
# gon-release v0.2.10
gon f4c3f2be10a2ee8d50067d2c8a8828a14c83977c darwin/* source
# govulncheck-release v1.7.0
govulncheck 617f44b718537dccdea1915395650e0529e3b72e linux/amd64 source
syft v1.42.3 linux/amd64 0d6be741479eddd2c8644a288990c04f3df0d609bbc1599a005532a9dff63509 https://github.com/anchore/syft/releases/download/v1.42.3/syft_1.42.3_linux_amd64.tar.gz
syft v1.42.3 darwin/arm64 d71ee7db2be0fe2e96f679fd9d69ef04274cc86c8604707797080a21070b3f32 https://github.com/anchore/syft/releases/download/v1.42.3/syft_1.42.3_darwin_arm64.tar.gz
syft v1.42.3 windows/amd64 e1b9f4945aa64c2b34970bec617623d7f803d0661b48a50b966768b363322e2d https://github.com/anchore/syft/releases/download/v1.42.3/syft_1.42.3_windows_amd64.zip
Expand Down
9 changes: 2 additions & 7 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ jobs:
if: inputs.go_source_hygiene == true || inputs.go_vulnerability_scan == true
uses: ./_workflows/.github/actions/locked-tools
with:
tools: go
tools: ${{ inputs.go_vulnerability_scan == true && 'go,govulncheck' || 'go' }}

- name: Checkout caller repo
if: inputs.go_source_hygiene == true || inputs.go_vulnerability_scan == true
Expand Down Expand Up @@ -382,13 +382,8 @@ jobs:
shell: bash
run: |
set -euo pipefail
go install golang.org/x/vuln/cmd/govulncheck@v1.7.0
# locked-tools does not put GOBIN or GOPATH/bin on PATH.
gopath="$(go env GOPATH)"
bin="$(go env GOBIN)"
bin="${bin:-${gopath%%:*}/bin}"
for goos in linux darwin windows; do
GOOS="$goos" "$bin/govulncheck" ./...
GOOS="$goos" govulncheck ./...
done

goreleaser-binaries:
Expand Down
Loading
Loading