Skip to content

Commit 677fb9a

Browse files
authored
ci: harden fetch_ctk with manifest/archive verification and optional redist-base-url (#3347)
ci: harden fetch_ctk and add redist-base-url Verify the redistrib manifest and archives before use, key the cache on the full manifest hash, and add a redist-base-url input for alternate servers. Harden install_windows_deps against WSL bash on PATH.
1 parent 32ba752 commit 677fb9a

4 files changed

Lines changed: 435 additions & 48 deletions

File tree

‎.github/actions/fetch_ctk/action.yml‎

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,35 +19,76 @@ inputs:
1919
description: "where the CTK components will be installed to, relative to $PWD"
2020
required: false
2121
default: "./cuda_toolkit"
22+
redist-base-url:
23+
description: >
24+
Base URL of a redistrib server (manifest and component archives).
25+
Defaults to the public NVIDIA developer server.
26+
Override to install from an alternate redistrib server.
27+
required: false
28+
default: "https://developer.download.nvidia.com/compute/cuda/redist"
2229

2330
runs:
2431
using: composite
2532
steps:
33+
- name: Install Unix dependencies
34+
if: ${{ runner.os == 'Linux' }}
35+
uses: ./.github/actions/install_unix_deps
36+
with:
37+
dependencies: "zstd curl xz-utils"
38+
dependent_exes: "zstd curl xz"
39+
40+
- name: Install Windows dependencies
41+
if: ${{ runner.os == 'Windows' }}
42+
uses: ./.github/actions/install_windows_deps
43+
with:
44+
dependent_exes: "curl unzip tar sha256sum cygpath"
45+
2646
- name: Set up CTK cache variable
2747
shell: bash --noprofile --norc -xeuo pipefail {0}
2848
run: |
29-
# Pre-process the component list to ensure hash uniqueness
3049
# Use the runtime workspace mount so this also works inside container jobs.
3150
CTK_REDIST_TOOL="${GITHUB_WORKSPACE}/ci/tools/fetch_ctk_redistrib.py"
32-
CTK_CACHE_COMPONENTS=${{ inputs.cuda-components }}
33-
CTK_JSON_URL="https://developer.download.nvidia.com/compute/cuda/redist/redistrib_${{ inputs.cuda-version }}.json"
51+
CTK_BASE_URL="${{ inputs.redist-base-url }}"
52+
# A trailing slash would defeat the public-server comparison below and double up in URLs.
53+
CTK_BASE_URL="${CTK_BASE_URL%/}"
54+
# RUNNER_TEMP persists across steps, so "Get CUDA components" reuses this file.
55+
CTK_JSON_FILE="$RUNNER_TEMP/redistrib-${{ inputs.cuda-version }}.json"
56+
curl -LSs --fail --retry 3 --retry-delay 2 "${CTK_BASE_URL}/redistrib_${{ inputs.cuda-version }}.json" -o "$CTK_JSON_FILE"
57+
58+
# Pin the metadata JSON: the per-archive sha256 checks below are only
59+
# trustworthy if the JSON that carries those hashes is itself trusted.
60+
# The pins describe the public server; alternate servers only warn.
61+
if [[ "$CTK_BASE_URL" == "https://developer.download.nvidia.com/compute/cuda/redist" ]]; then
62+
METADATA_SOURCE=public
63+
else
64+
METADATA_SOURCE=custom
65+
fi
66+
python "$CTK_REDIST_TOOL" verify-metadata \
67+
--cuda-version "${{ inputs.cuda-version }}" \
68+
--metadata-path "$CTK_JSON_FILE" \
69+
--metadata-source "$METADATA_SOURCE"
70+
71+
# Pre-process the component list to ensure hash uniqueness
3472
CTK_CACHE_COMPONENTS="$(python "$CTK_REDIST_TOOL" filter-components \
3573
--host-platform "${{ inputs.host-platform }}" \
3674
--cuda-version "${{ inputs.cuda-version }}" \
37-
--components "$CTK_CACHE_COMPONENTS" \
38-
--metadata-url "$CTK_JSON_URL")"
75+
--components "${{ inputs.cuda-components }}" \
76+
--metadata-path "$CTK_JSON_FILE")"
3977
78+
# Alternate redist servers (non-default) publish different manifests for the
79+
# same CUDA version; keying on the full manifest hash keeps them from restoring
80+
# each other's toolkit (a truncated hash could be collided on purpose).
81+
# Hash via stdin: with a backslash in the path (Windows RUNNER_TEMP), sha256sum
82+
# prefixes its output line with "\", which would end up in the cache key.
83+
MANIFEST_SHA256="$(sha256sum < "$CTK_JSON_FILE" | awk '{print $1}')"
4084
HASH=$(echo -n "${CTK_CACHE_COMPONENTS}" | sha256sum | awk '{print $1}')
41-
echo "CTK_CACHE_KEY=mini-ctk-${{ inputs.cuda-version }}-${{ inputs.host-platform }}-$HASH" >> $GITHUB_ENV
42-
echo "CTK_CACHE_FILENAME=mini-ctk-${{ inputs.cuda-version }}-${{ inputs.host-platform }}-$HASH.tar.gz" >> $GITHUB_ENV
43-
echo "CTK_CACHE_COMPONENTS=${CTK_CACHE_COMPONENTS}" >> $GITHUB_ENV
44-
45-
- name: Install dependencies
46-
uses: ./.github/actions/install_unix_deps
47-
continue-on-error: false
48-
with:
49-
dependencies: "zstd curl xz-utils"
50-
dependent_exes: "zstd curl xz"
85+
{
86+
echo "CTK_CACHE_KEY=mini-ctk-${{ inputs.cuda-version }}-${{ inputs.host-platform }}-$HASH-m$MANIFEST_SHA256"
87+
echo "CTK_CACHE_FILENAME=mini-ctk-${{ inputs.cuda-version }}-${{ inputs.host-platform }}-$HASH-m$MANIFEST_SHA256.tar.gz"
88+
echo "CTK_CACHE_COMPONENTS=${CTK_CACHE_COMPONENTS}"
89+
echo "CTK_BASE_URL=${CTK_BASE_URL}"
90+
echo "CTK_JSON_FILE=${CTK_JSON_FILE}"
91+
} >> "$GITHUB_ENV"
5192
5293
- name: Download CTK cache
5394
id: ctk-get-cache
@@ -68,13 +109,10 @@ runs:
68109
rm -rf $CACHE_TMP_DIR
69110
mkdir $CACHE_TMP_DIR
70111
71-
# The binary archives (redist) are guaranteed to be updated as part of the release posting.
72112
# Use the runtime workspace mount so this also works inside container jobs.
73113
CTK_REDIST_TOOL="${GITHUB_WORKSPACE}/ci/tools/fetch_ctk_redistrib.py"
74-
CTK_BASE_URL="https://developer.download.nvidia.com/compute/cuda/redist/"
75-
CTK_JSON_URL="$CTK_BASE_URL/redistrib_${{ inputs.cuda-version }}.json"
76-
CTK_JSON_FILE="$CACHE_TMP_DIR/redistrib.json"
77-
curl -LSs "$CTK_JSON_URL" -o "$CTK_JSON_FILE"
114+
# CTK_BASE_URL and the verified manifest at CTK_JSON_FILE come from
115+
# "Set up CTK cache variable".
78116
if [[ "${{ inputs.host-platform }}" == linux* ]]; then
79117
function extract() {
80118
tar -xvf $1 -C $CACHE_TMP_DIR --strip-components=1
@@ -102,6 +140,13 @@ runs:
102140
CTK_COMPONENT_URL="${CTK_BASE_URL}/${CTK_COMPONENT_REL_PATH}"
103141
CTK_COMPONENT_COMPONENT_FILENAME="$(basename $CTK_COMPONENT_REL_PATH)"
104142
download $CTK_COMPONENT_URL $CTK_COMPONENT_COMPONENT_FILENAME
143+
# Verify the downloaded archive against the sha256 in the (pinned)
144+
# metadata before extracting; fail closed on any mismatch.
145+
CTK_COMPONENT_SHA256="$(python "$CTK_REDIST_TOOL" component-sha256 \
146+
--host-platform "${{ inputs.host-platform }}" \
147+
--component "$CTK_COMPONENT" \
148+
--metadata-path "$CTK_JSON_FILE")"
149+
echo "${CTK_COMPONENT_SHA256} ${CTK_COMPONENT_COMPONENT_FILENAME}" | sha256sum --check --strict -
105150
extract $CTK_COMPONENT_COMPONENT_FILENAME
106151
rm $CTK_COMPONENT_COMPONENT_FILENAME
107152
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
name: Install dependencies on Windows
5+
6+
description: Install Git Bash tools from a verified portable Git bundle when required executables are missing.
7+
8+
inputs:
9+
dependent_exes:
10+
description: Space-separated executable names provided by Git for Windows
11+
required: true
12+
13+
runs:
14+
using: composite
15+
steps:
16+
- name: Install dependencies
17+
shell: powershell
18+
env:
19+
DEPENDENT_EXES: ${{ inputs.dependent_exes }}
20+
run: |
21+
$ErrorActionPreference = 'Stop'
22+
if ($env:DEPENDENT_EXES -notmatch '^\s*[a-zA-Z0-9_.-]+(\s+[a-zA-Z0-9_.-]+)*\s*$') {
23+
throw 'Expected a space-separated list of executable names.'
24+
}
25+
# Fail the check if the list did not reach Bash (a WSL bash would not receive it).
26+
# Keep double quotes out of $check: Windows PowerShell 5.1 strips them when
27+
# passing arguments to native commands. The names are validated above.
28+
$check = 'missing=0; set -- $DEPENDENT_EXES; [ $# -gt 0 ] || missing=1; for tool in $DEPENDENT_EXES; do command -v $tool || missing=1; done; exit $missing'
29+
# Skip the WSL launcher in System32/WindowsApps: it is not a Git for Windows bash.
30+
$bash = Get-Command bash -All -ErrorAction SilentlyContinue |
31+
Where-Object { $_.Source -notmatch '\\(System32|SysWOW64|WindowsApps)\\' } |
32+
Select-Object -First 1
33+
if ($bash) {
34+
& $bash.Source --noprofile --norc -c $check
35+
if ($LASTEXITCODE -eq 0) {
36+
# Later `shell: bash` steps resolve `bash` through PATH. If that is not the
37+
# Git Bash validated above (e.g. the WSL launcher comes first), put it first.
38+
$pathBash = Get-Command bash -ErrorAction SilentlyContinue
39+
if ($pathBash -and ($pathBash.Source -ne $bash.Source)) {
40+
$bashDir = Split-Path -Parent $bash.Source
41+
$env:PATH = "$bashDir;$env:PATH"
42+
$bashDir | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
43+
Write-Host "Prepended $bashDir to PATH so steps use Git Bash instead of $($pathBash.Source)."
44+
}
45+
Write-Host 'All dependencies are found. Do nothing.'
46+
exit 0
47+
}
48+
}
49+
50+
$architecture = [Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString()
51+
switch ($architecture) {
52+
'Arm64' {
53+
$package = 'arm64'
54+
$expectedHash = '3bf26b94d9399b16a890776e468334f501742861576cbcdea2d9134643c374bd'
55+
}
56+
'X64' {
57+
$package = '64-bit'
58+
$expectedHash = 'ab00566336b5472120f9a52d34f2e79c5406535792acb0548001ffd0bd090e5d'
59+
}
60+
default { throw "Unsupported Windows architecture: $architecture" }
61+
}
62+
$root = Join-Path $env:RUNNER_TOOL_CACHE "WindowsDeps\Git-2.55.0.3-$package"
63+
$complete = "$root.complete"
64+
if (-not (Test-Path -LiteralPath $complete)) {
65+
$archive = Join-Path $env:RUNNER_TEMP "PortableGit-2.55.0.3-$package.7z.exe"
66+
try {
67+
Invoke-WebRequest -UseBasicParsing -Uri "https://github.com/git-for-windows/git/releases/download/v2.55.0.windows.3/PortableGit-2.55.0.3-$package.7z.exe" -OutFile $archive
68+
$actualHash = (Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash.ToLowerInvariant()
69+
if ($actualHash -ne $expectedHash) {
70+
throw "Portable Git checksum mismatch: expected $expectedHash, got $actualHash."
71+
}
72+
if (Test-Path -LiteralPath $root) {
73+
Remove-Item -LiteralPath $root -Recurse -Force
74+
}
75+
$process = Start-Process -FilePath $archive -ArgumentList @('-y', ('-o"{0}"' -f $root)) -Wait -PassThru
76+
if ($process.ExitCode -ne 0) {
77+
throw "Portable Git extraction failed with exit code $($process.ExitCode)."
78+
}
79+
}
80+
finally {
81+
if (Test-Path -LiteralPath $archive) {
82+
Remove-Item -LiteralPath $archive -Force
83+
}
84+
}
85+
}
86+
$env:PATH = "$root\bin;$root\usr\bin;$env:PATH"
87+
& "$root\bin\bash.exe" --noprofile --norc -c $check
88+
if ($LASTEXITCODE -ne 0) {
89+
throw 'Required executables are still missing after installing portable Git.'
90+
}
91+
New-Item -ItemType File -Force -Path $complete | Out-Null
92+
@("$root\bin", "$root\usr\bin") | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append

0 commit comments

Comments
 (0)